react-native-vroom-chart 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/android/CMakeLists.txt +31 -42
- package/android/README.md +23 -21
- package/android/build.gradle +26 -8
- package/cpp/VroomChartHostObject.cpp +14 -10
- package/cpp/VroomFontMgr.cpp +25 -0
- package/cpp/VroomFontMgr.h +14 -0
- package/cpp/VroomJsiInstaller.cpp +31 -12
- package/cpp/_core_src/chart.cpp +11 -3
- package/cpp/_core_src/chart.h +7 -0
- package/cpp/_core_src/chart_internal.h +2 -2
- package/cpp/_core_src/crosshair.cpp +5 -3
- package/cpp/_core_src/fonts.h +3 -3
- package/cpp/_core_src/labels.cpp +31 -7
- package/cpp/_core_src/ma_overlay.h +5 -0
- package/cpp/_core_src/price_format.cpp +87 -0
- package/cpp/_core_src/price_format.h +69 -0
- package/cpp/_core_src/price_indicator.cpp +5 -3
- package/cpp/_core_src/price_lines.cpp +7 -5
- package/cpp/_core_src/ticks.cpp +0 -14
- package/cpp/_core_src/ticks.h +2 -8
- package/cpp/_core_src/tip_anchor.h +49 -0
- package/package.json +7 -5
- package/react-native-vroom-chart.podspec +31 -11
- package/cpp/VroomSkiaContext.cpp +0 -33
- package/cpp/VroomSkiaContext.h +0 -30
package/README.md
CHANGED
|
@@ -16,8 +16,11 @@ import { VroomChart } from 'react-native-vroom-chart';
|
|
|
16
16
|
## Peer dependencies
|
|
17
17
|
|
|
18
18
|
- `react-native` ≥ 0.76
|
|
19
|
-
- `@shopify/react-native-skia` ≥
|
|
19
|
+
- `@shopify/react-native-skia` ≥ 2.11.0 — provides the Skia runtime we draw into. vroom links RN-Skia's native internals (`cpp/api`) and tracks that package closely; those headers have no semver guarantee.
|
|
20
|
+
|
|
20
21
|
- `react-native-gesture-handler` ≥ 2.16 — long-press / pan / pinch recognition
|
|
22
|
+
- `react-native-reanimated` ≥ 4.0 — gesture worklets
|
|
23
|
+
- `react-native-worklets` ≥ 0.7.0 — required by Skia 2.11's Reanimated integration on native
|
|
21
24
|
|
|
22
25
|
## Status
|
|
23
26
|
|
package/android/CMakeLists.txt
CHANGED
|
@@ -4,20 +4,21 @@
|
|
|
4
4
|
# core the iOS podspec compiles (see ../react-native-vroom-chart.podspec),
|
|
5
5
|
# plus this directory's small JNI entry point (src/main/cpp/VroomChartJni.cpp).
|
|
6
6
|
#
|
|
7
|
-
# RN-Skia's headers (
|
|
8
|
-
#
|
|
9
|
-
# instead we point directly at @shopify/react-native-skia's `cpp/`
|
|
10
|
-
# mirroring the podspec's
|
|
11
|
-
#
|
|
7
|
+
# RN-Skia's headers (JsiSkNativeObjects.h, JsiSkPicture.h, and the
|
|
8
|
+
# RNSkPlatformContext.h they pull in) aren't consumed via CMake's prefab
|
|
9
|
+
# mechanism; instead we point directly at @shopify/react-native-skia's `cpp/`
|
|
10
|
+
# sources, mirroring the podspec's
|
|
11
|
+
# `require.resolve('@shopify/react-native-skia/package.json')` trick
|
|
12
|
+
# (VROOM_SKIA_CPP_DIR is computed the same way in build.gradle).
|
|
12
13
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
14
|
+
# Prebuilt libskia.a lives in the sibling npm package react-native-skia-android
|
|
15
|
+
# (or react-native-skia-graphite-android). build.gradle resolves that package
|
|
16
|
+
# and passes VROOM_SKIA_ANDROID_LIBS_DIR = <pkg>/libs; we append ${ANDROID_ABI}.
|
|
17
|
+
#
|
|
18
|
+
# SK_GRAPHITE vs SK_GL/SK_GANESH must match RN-Skia's own build so our direct
|
|
19
|
+
# Skia calls see the same ABI. Graphite is detected from the
|
|
20
|
+
# <skia-pkg>/libs/.graphite marker (same file RN-Skia uses) and passed in as
|
|
21
|
+
# VROOM_SKIA_GRAPHITE.
|
|
21
22
|
cmake_minimum_required(VERSION 3.22)
|
|
22
23
|
project(vroomchart)
|
|
23
24
|
|
|
@@ -35,44 +36,30 @@ find_package(shopify_react-native-skia REQUIRED CONFIG)
|
|
|
35
36
|
if(NOT DEFINED VROOM_SKIA_CPP_DIR)
|
|
36
37
|
message(FATAL_ERROR "VROOM_SKIA_CPP_DIR not set — passed from android/build.gradle.")
|
|
37
38
|
endif()
|
|
39
|
+
if(NOT DEFINED VROOM_SKIA_ANDROID_LIBS_DIR)
|
|
40
|
+
message(FATAL_ERROR "VROOM_SKIA_ANDROID_LIBS_DIR not set — passed from android/build.gradle.")
|
|
41
|
+
endif()
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
# Skia static libs live at <root>/libs/android/<abi>/libskia.a.
|
|
41
|
-
get_filename_component(VROOM_SKIA_PKG_DIR "${VROOM_SKIA_CPP_DIR}" DIRECTORY)
|
|
42
|
-
set(VROOM_SKIA_LIBS_DIR "${VROOM_SKIA_PKG_DIR}/libs/android/${ANDROID_ABI}")
|
|
43
|
+
set(VROOM_SKIA_LIBS_DIR "${VROOM_SKIA_ANDROID_LIBS_DIR}/${ANDROID_ABI}")
|
|
43
44
|
|
|
44
45
|
# Our chart core calls Skia types (SkCanvas, SkPaint, SkFont, ...) and
|
|
45
|
-
# RN-Skia's own JSI plumbing (
|
|
46
|
+
# RN-Skia's own JSI plumbing (JsiSkPicture / NativeObject, ...)
|
|
46
47
|
# directly — none of that is header-only, and we need *both* libraries:
|
|
47
48
|
# - librnskia.so (RN-Skia's prefab-published `rnskia` module, consumed above
|
|
48
|
-
# via `implementation project(":shopify_react-native-skia")`) for
|
|
49
|
-
# own symbols (
|
|
50
|
-
# - libskia.a (
|
|
51
|
-
# calls (SkCanvas::drawRect, SkFont, ...): librnskia.so
|
|
52
|
-
# internally but doesn't re-export its symbols, so linking
|
|
53
|
-
# alone leaves every direct Skia call undefined.
|
|
49
|
+
# via `implementation project(":shopify_react-native-skia")`) for RN-Skia's
|
|
50
|
+
# own symbols (JsiSkPicture, NativeObject, ...), which only exist there.
|
|
51
|
+
# - libskia.a (the prebuilt Skia static lib from react-native-skia-android)
|
|
52
|
+
# for direct Skia calls (SkCanvas::drawRect, SkFont, ...): librnskia.so
|
|
53
|
+
# links libskia.a internally but doesn't re-export its symbols, so linking
|
|
54
|
+
# librnskia.so alone leaves every direct Skia call undefined.
|
|
54
55
|
# Recompiling RN-Skia's cpp/jsi/*.cpp ourselves instead of linking librnskia.so
|
|
55
|
-
# was considered and rejected — it would duplicate
|
|
56
|
+
# was considered and rejected — it would duplicate RN-Skia's symbols (and their
|
|
56
57
|
# static/global state) across two separately-loaded .so's, which is fine on
|
|
57
58
|
# iOS's single static binary but not here.
|
|
58
59
|
add_library(vroom_skia_prebuilt STATIC IMPORTED)
|
|
59
60
|
set_property(TARGET vroom_skia_prebuilt PROPERTY
|
|
60
61
|
IMPORTED_LOCATION "${VROOM_SKIA_LIBS_DIR}/libskia.a")
|
|
61
62
|
|
|
62
|
-
set(SK_GRAPHITE_AVAILABLE OFF)
|
|
63
|
-
if(EXISTS "${VROOM_SKIA_LIBS_DIR}/libskia.a")
|
|
64
|
-
execute_process(
|
|
65
|
-
COMMAND nm "${VROOM_SKIA_LIBS_DIR}/libskia.a"
|
|
66
|
-
COMMAND grep "dawn::\\|wgpu\\|_ZN4dawn\\|DawnDevice\\|dawn_native"
|
|
67
|
-
OUTPUT_VARIABLE VROOM_NM_OUTPUT
|
|
68
|
-
ERROR_QUIET
|
|
69
|
-
RESULT_VARIABLE VROOM_NM_RESULT
|
|
70
|
-
)
|
|
71
|
-
if(VROOM_NM_RESULT EQUAL 0 AND NOT "${VROOM_NM_OUTPUT}" STREQUAL "")
|
|
72
|
-
set(SK_GRAPHITE_AVAILABLE ON)
|
|
73
|
-
endif()
|
|
74
|
-
endif()
|
|
75
|
-
|
|
76
63
|
# The chart core (shared across iOS/Android/WASM — see ../../core/CMakeLists.txt).
|
|
77
64
|
set(VROOM_CORE_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../cpp/_core_include")
|
|
78
65
|
set(VROOM_CORE_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../cpp/_core_src")
|
|
@@ -81,7 +68,7 @@ file(GLOB VROOM_CORE_SOURCES CONFIGURE_DEPENDS "${VROOM_CORE_SRC_DIR}/*.cpp")
|
|
|
81
68
|
add_library(vroomchart SHARED
|
|
82
69
|
"${CMAKE_CURRENT_SOURCE_DIR}/../cpp/VroomJsiInstaller.cpp"
|
|
83
70
|
"${CMAKE_CURRENT_SOURCE_DIR}/../cpp/VroomChartHostObject.cpp"
|
|
84
|
-
"${CMAKE_CURRENT_SOURCE_DIR}/../cpp/
|
|
71
|
+
"${CMAKE_CURRENT_SOURCE_DIR}/../cpp/VroomFontMgr.cpp"
|
|
85
72
|
${VROOM_CORE_SOURCES}
|
|
86
73
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/VroomChartJni.cpp"
|
|
87
74
|
)
|
|
@@ -101,7 +88,9 @@ target_include_directories(vroomchart PRIVATE
|
|
|
101
88
|
"${VROOM_SKIA_CPP_DIR}/dawn/include"
|
|
102
89
|
# RNSkPlatformContext.h pulls in ReactCommon/CallInvoker.h directly, which
|
|
103
90
|
# isn't part of the prefab-published jsi headers — same dirs RN-Skia's own
|
|
104
|
-
# android/CMakeLists.txt adds for the same reason.
|
|
91
|
+
# android/CMakeLists.txt adds for the same reason. Still required: we include
|
|
92
|
+
# JsiSkPicture.h, which includes JsiSkNativeObjects.h, which includes
|
|
93
|
+
# RNSkPlatformContext.h.
|
|
105
94
|
"${REACT_NATIVE_DIR}/ReactCommon"
|
|
106
95
|
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
|
|
107
96
|
"${REACT_NATIVE_DIR}/ReactCommon/runtimeexecutor"
|
|
@@ -114,7 +103,7 @@ target_compile_definitions(vroomchart PRIVATE
|
|
|
114
103
|
ONANDROID
|
|
115
104
|
ON_ANDROID
|
|
116
105
|
)
|
|
117
|
-
if(
|
|
106
|
+
if(VROOM_SKIA_GRAPHITE)
|
|
118
107
|
target_compile_definitions(vroomchart PRIVATE SK_GRAPHITE)
|
|
119
108
|
else()
|
|
120
109
|
target_compile_definitions(vroomchart PRIVATE SK_GL SK_GANESH)
|
package/android/README.md
CHANGED
|
@@ -8,31 +8,33 @@ Android equivalent of `../ios/`: a small TurboModule
|
|
|
8
8
|
[`../cpp/VroomJsiInstaller.cpp`](../cpp/VroomJsiInstaller.cpp) the iOS bridge
|
|
9
9
|
uses.
|
|
10
10
|
|
|
11
|
-
The chart core
|
|
12
|
-
(`../cpp/
|
|
13
|
-
|
|
11
|
+
The chart core (`../cpp/_core_src`) and the platform font-manager helper
|
|
12
|
+
(`../cpp/VroomFontMgr.*`) are unmodified and shared across iOS and Android —
|
|
13
|
+
only the platform glue (this directory + `../ios/`) differs.
|
|
14
14
|
`../cpp/VroomChartHostObject.cpp` has one small `#if defined(__ANDROID__)`
|
|
15
15
|
branch (see "Cross-`.so` Skia objects" below).
|
|
16
16
|
|
|
17
17
|
`build.gradle` resolves `@shopify/react-native-skia`'s `cpp/` sources via
|
|
18
18
|
Node module resolution (mirroring `../react-native-vroom-chart.podspec`'s
|
|
19
|
-
`require.resolve` trick) so
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
`require.resolve` trick) so we can include RN-Skia's `JsiSkPicture.h` /
|
|
20
|
+
`JsiSkNativeObjects.h`. Android prebuilt `libskia.a` lives in the sibling
|
|
21
|
+
`react-native-skia-android` package (or `react-native-skia-graphite-android`
|
|
22
|
+
when `<skia-pkg>/libs/.graphite` exists); Gradle resolves that package and
|
|
23
|
+
passes its `libs/` dir to CMake. Graphite vs Ganesh is the same marker file
|
|
24
|
+
RN-Skia uses, so `SK_GRAPHITE` / `SK_GL`+`SK_GANESH` match RN-Skia's own
|
|
25
|
+
build — needed for vroom's direct Skia calls, not for a virtual call into
|
|
26
|
+
RN-Skia (we construct the font manager ourselves).
|
|
26
27
|
|
|
27
28
|
We link two RN-Skia-adjacent libraries into `libvroomchart.so`, for two
|
|
28
29
|
different reasons:
|
|
29
30
|
- `shopify_react-native-skia::rnskia` (RN-Skia's own prefab-published
|
|
30
|
-
shared library, `librnskia.so`) — for `
|
|
31
|
-
whose out-of-line methods are compiled only there.
|
|
32
|
-
-
|
|
33
|
-
prefab-published) — for direct Skia calls
|
|
34
|
-
|
|
35
|
-
re-export its
|
|
31
|
+
shared library, `librnskia.so`) — for `JsiSkPicture` / `NativeObject`
|
|
32
|
+
and friends, whose out-of-line methods are compiled only there.
|
|
33
|
+
- `libskia.a` from `react-native-skia-android` (imported directly by path,
|
|
34
|
+
since it isn't prefab-published) — for direct Skia calls
|
|
35
|
+
(`SkCanvas::drawRect`, `SkFont`, `SkFontMgr_New_Android`, ...), since
|
|
36
|
+
`librnskia.so` links `libskia.a` internally but doesn't re-export its
|
|
37
|
+
symbols.
|
|
36
38
|
|
|
37
39
|
### Cross-`.so` Skia objects
|
|
38
40
|
|
|
@@ -43,11 +45,11 @@ of the JSI bridge, but **`RNSkia::JsiSkPicture` — the object `render()` /
|
|
|
43
45
|
`libvroomchart.so` on Android.** RN-Skia's own C++ (e.g.
|
|
44
46
|
`cpp/api/recorder/Convertor.h`'s `getPropertyValue<sk_sp<SkPicture>>`, which
|
|
45
47
|
runs whenever `<Picture>` reads the value) does
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
different, unmerged RTTI record for "the same" class and fails with
|
|
50
|
-
|
|
48
|
+
`getJsiObject<JsiSkPicture>(rt, value)`, a `dynamic_pointer_cast` under the
|
|
49
|
+
hood. A `JsiSkPicture` we construct ourselves has a vtable/typeinfo compiled
|
|
50
|
+
into *our* `.so`; that cast — running inside `librnskia.so` — sees a
|
|
51
|
+
different, unmerged RTTI record for "the same" class and fails with
|
|
52
|
+
`Expected a Skia object of a different type`.
|
|
51
53
|
|
|
52
54
|
`VroomChartHostObject.cpp`'s `wrapPicture()` works around this only on
|
|
53
55
|
Android: instead of constructing `RNSkia::JsiSkPicture` directly (as iOS
|
package/android/build.gradle
CHANGED
|
@@ -28,7 +28,23 @@ def resolvePackageDir(packageJsonSpecifier) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
def reactNativeDir = resolvePackageDir("react-native/package.json")
|
|
31
|
-
def
|
|
31
|
+
def skiaPkgDir = resolvePackageDir("@shopify/react-native-skia/package.json")
|
|
32
|
+
def skiaCppDir = new File(skiaPkgDir, "cpp")
|
|
33
|
+
|
|
34
|
+
// Skia 2.10+ ships Android prebuilts in react-native-skia-android (Ganesh)
|
|
35
|
+
// or react-native-skia-graphite-android (Graphite). Graphite is selected by
|
|
36
|
+
// the same libs/.graphite marker RN-Skia uses. Resolve from the skia package
|
|
37
|
+
// so pnpm's nested .pnpm layout still finds the sibling binary package.
|
|
38
|
+
def useGraphite = new File(skiaPkgDir, "libs/.graphite").exists()
|
|
39
|
+
def skiaAndroidPkgName = useGraphite
|
|
40
|
+
? "react-native-skia-graphite-android"
|
|
41
|
+
: "react-native-skia-android"
|
|
42
|
+
def skiaAndroidPkgJson = providers.exec {
|
|
43
|
+
workingDir(rootDir)
|
|
44
|
+
commandLine("node", "--print",
|
|
45
|
+
"require.resolve('${skiaAndroidPkgName}/package.json', { paths: ['${skiaPkgDir}'] })")
|
|
46
|
+
}.standardOutput.asText.get().trim()
|
|
47
|
+
def skiaAndroidLibsDir = new File(file(skiaAndroidPkgJson).parentFile, "libs")
|
|
32
48
|
|
|
33
49
|
// Mirror ../cpp/_core_{src,include} from ../../core/{src,include} whenever
|
|
34
50
|
// the monorepo core is present, so edits to packages/core/ propagate on the
|
|
@@ -72,7 +88,9 @@ android {
|
|
|
72
88
|
abiFilters(*reactNativeArchitectures())
|
|
73
89
|
arguments "-DANDROID_STL=c++_shared",
|
|
74
90
|
"-DREACT_NATIVE_DIR=${reactNativeDir}",
|
|
75
|
-
"-DVROOM_SKIA_CPP_DIR=${skiaCppDir}"
|
|
91
|
+
"-DVROOM_SKIA_CPP_DIR=${skiaCppDir}",
|
|
92
|
+
"-DVROOM_SKIA_ANDROID_LIBS_DIR=${skiaAndroidLibsDir}",
|
|
93
|
+
"-DVROOM_SKIA_GRAPHITE=${useGraphite ? 'ON' : 'OFF'}"
|
|
76
94
|
}
|
|
77
95
|
}
|
|
78
96
|
}
|
|
@@ -105,11 +123,11 @@ repositories {
|
|
|
105
123
|
|
|
106
124
|
dependencies {
|
|
107
125
|
implementation "com.facebook.react:react-android"
|
|
108
|
-
// ../cpp/VroomChartHostObject.cpp constructs RN-Skia JSI
|
|
109
|
-
// (JsiSkPicture, etc.) whose base
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
126
|
+
// ../cpp/VroomChartHostObject.cpp constructs RN-Skia JSI objects
|
|
127
|
+
// (JsiSkPicture, etc.) whose NativeObject base has out-of-line methods
|
|
128
|
+
// compiled into RN-Skia's own native library — we link against its
|
|
129
|
+
// prefab-published `rnskia` package (below, in CMakeLists.txt) rather than
|
|
130
|
+
// recompiling those .cpp files ourselves, to avoid duplicate definitions of
|
|
131
|
+
// RN-Skia's internals across two separately-loaded .so's.
|
|
114
132
|
implementation project(":shopify_react-native-skia")
|
|
115
133
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
#include "vroom/vroom_chart.h"
|
|
9
9
|
|
|
10
10
|
// RN-Skia headers.
|
|
11
|
-
#include "
|
|
11
|
+
#include "JsiSkNativeObjects.h"
|
|
12
12
|
#include "JsiSkPicture.h"
|
|
13
13
|
|
|
14
14
|
namespace vroom {
|
|
@@ -78,9 +78,9 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
|
78
78
|
// does, below) gives it a vtable/typeinfo from *our* .so; when RN-Skia's own
|
|
79
79
|
// compiled code later consumes it (e.g. Convertor.h's
|
|
80
80
|
// `getPropertyValue<sk_sp<SkPicture>>`, which does
|
|
81
|
-
// `
|
|
82
|
-
// under the hood), the cast fails cross-.so with "
|
|
83
|
-
//
|
|
81
|
+
// `getJsiObject<JsiSkPicture>(rt, value)` — a dynamic_pointer_cast
|
|
82
|
+
// under the hood), the cast fails cross-.so with "Expected a Skia object of
|
|
83
|
+
// a different type", because the object's *runtime* type was never actually
|
|
84
84
|
// compiled inside librnskia.so. iOS statically links everything into one
|
|
85
85
|
// binary, so no such mismatch exists there.
|
|
86
86
|
//
|
|
@@ -122,26 +122,30 @@ static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
|
|
|
122
122
|
if (!skiaApi.isObject()) return facebook::jsi::Value::null();
|
|
123
123
|
auto pictureFactory = skiaApi.asObject(rt).getProperty(rt, "Picture");
|
|
124
124
|
if (!pictureFactory.isObject()) return facebook::jsi::Value::null();
|
|
125
|
+
auto pictureFactoryObj = pictureFactory.asObject(rt);
|
|
125
126
|
auto makePicture =
|
|
126
|
-
|
|
127
|
+
pictureFactoryObj.getPropertyAsFunction(rt, "MakePicture");
|
|
127
128
|
|
|
128
129
|
// Mirrors JsiSkPictureFactory::MakePicture's expected argument shape: an
|
|
129
130
|
// object with a `.buffer` property holding the ArrayBuffer (matching a
|
|
130
131
|
// Uint8Array-like value; the factory ignores everything else about it).
|
|
132
|
+
//
|
|
133
|
+
// Skia 2.11's NativeState host methods recover the factory via `this`
|
|
134
|
+
// (`fromThis` → "Expected PictureFactory but got non-object" if unbound).
|
|
131
135
|
jsi::Object arg(rt);
|
|
132
136
|
arg.setProperty(rt, "buffer", arrayBuffer);
|
|
133
|
-
return makePicture.
|
|
137
|
+
return makePicture.callWithThis(rt, pictureFactoryObj, arg);
|
|
134
138
|
}
|
|
135
139
|
#else
|
|
136
|
-
// Shared helper: wraps a fresh picture for return to JS
|
|
137
|
-
//
|
|
140
|
+
// Shared helper: wraps a fresh picture for return to JS. Memory pressure is
|
|
141
|
+
// applied inside JsiSkPicture::create (NativeObject::create reads
|
|
142
|
+
// getMemoryPressure() → picture->approximateBytesUsed()).
|
|
138
143
|
static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
|
|
139
144
|
const sk_sp<SkPicture>& pic) {
|
|
140
145
|
if (!pic) return facebook::jsi::Value::null();
|
|
141
146
|
auto host =
|
|
142
147
|
std::make_shared<RNSkia::JsiSkPicture>(/*context=*/nullptr, pic);
|
|
143
|
-
return
|
|
144
|
-
/*context=*/nullptr);
|
|
148
|
+
return RNSkia::makeJsiObject(rt, host);
|
|
145
149
|
}
|
|
146
150
|
#endif
|
|
147
151
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#include "VroomFontMgr.h"
|
|
2
|
+
|
|
3
|
+
#pragma clang diagnostic push
|
|
4
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
5
|
+
#if defined(__APPLE__)
|
|
6
|
+
#include "include/ports/SkFontMgr_mac_ct.h"
|
|
7
|
+
#elif defined(__ANDROID__)
|
|
8
|
+
#include "include/ports/SkFontMgr_android.h"
|
|
9
|
+
#include "include/ports/SkFontScanner_FreeType.h"
|
|
10
|
+
#endif
|
|
11
|
+
#pragma clang diagnostic pop
|
|
12
|
+
|
|
13
|
+
namespace vroom {
|
|
14
|
+
|
|
15
|
+
sk_sp<SkFontMgr> makePlatformFontMgr() {
|
|
16
|
+
#if defined(__APPLE__)
|
|
17
|
+
return SkFontMgr_New_CoreText(nullptr);
|
|
18
|
+
#elif defined(__ANDROID__)
|
|
19
|
+
return SkFontMgr_New_Android(nullptr, SkFontScanner_Make_FreeType());
|
|
20
|
+
#else
|
|
21
|
+
return nullptr;
|
|
22
|
+
#endif
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
} // namespace vroom
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#pragma clang diagnostic push
|
|
4
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
5
|
+
#include "include/core/SkFontMgr.h"
|
|
6
|
+
#include "include/core/SkRefCnt.h"
|
|
7
|
+
#pragma clang diagnostic pop
|
|
8
|
+
|
|
9
|
+
namespace vroom {
|
|
10
|
+
|
|
11
|
+
// Returns the platform system font manager, or nullptr if unavailable.
|
|
12
|
+
sk_sp<SkFontMgr> makePlatformFontMgr();
|
|
13
|
+
|
|
14
|
+
} // namespace vroom
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
#include <memory>
|
|
16
16
|
|
|
17
17
|
#include "VroomChartHostObject.h"
|
|
18
|
-
#include "
|
|
18
|
+
#include "VroomFontMgr.h"
|
|
19
19
|
|
|
20
20
|
#include "chart_internal.h"
|
|
21
21
|
|
|
@@ -26,20 +26,36 @@
|
|
|
26
26
|
#include "include/core/SkTypeface.h"
|
|
27
27
|
#pragma clang diagnostic pop
|
|
28
28
|
|
|
29
|
+
#if defined(__ANDROID__)
|
|
30
|
+
#include <android/log.h>
|
|
31
|
+
#define VROOM_WARN(...) \
|
|
32
|
+
__android_log_print(ANDROID_LOG_WARN, "VroomChart", __VA_ARGS__)
|
|
33
|
+
#elif defined(__APPLE__)
|
|
34
|
+
#include <os/log.h>
|
|
35
|
+
#define VROOM_WARN(fmt, ...) \
|
|
36
|
+
os_log_error(OS_LOG_DEFAULT, "VroomChart: " fmt, ##__VA_ARGS__)
|
|
37
|
+
#else
|
|
38
|
+
#define VROOM_WARN(...)
|
|
39
|
+
#endif
|
|
40
|
+
|
|
29
41
|
namespace vroom {
|
|
30
42
|
|
|
31
43
|
namespace jsi = facebook::jsi;
|
|
32
44
|
|
|
33
|
-
// Loads the system typeface once via
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
45
|
+
// Loads the system typeface once via the platform SkFontMgr and hands it to
|
|
46
|
+
// the chart core. Built only once — constructing a font manager enumerates
|
|
47
|
+
// the system font set and is not cheap. Failure is logged once; we do not
|
|
48
|
+
// retry, because this no longer depends on RN-Skia's JSI bindings appearing.
|
|
49
|
+
static void ensureAxisTypeface() {
|
|
37
50
|
static bool done = false;
|
|
38
51
|
if (done) return;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
auto mgr =
|
|
42
|
-
if (!mgr)
|
|
52
|
+
done = true;
|
|
53
|
+
|
|
54
|
+
auto mgr = vroom::makePlatformFontMgr();
|
|
55
|
+
if (!mgr) {
|
|
56
|
+
VROOM_WARN("axis typeface: platform font manager unavailable");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
43
59
|
|
|
44
60
|
// A null family name is meant to request "the platform default" — CoreText
|
|
45
61
|
// (iOS) honors that, but Android's SkFontMgr_New_Android does not: it
|
|
@@ -51,12 +67,16 @@ static void ensureAxisTypeface(jsi::Runtime& runtime) {
|
|
|
51
67
|
if (!tf) {
|
|
52
68
|
tf = mgr->matchFamilyStyle("sans-serif", SkFontStyle());
|
|
53
69
|
}
|
|
54
|
-
if (!tf)
|
|
70
|
+
if (!tf) {
|
|
71
|
+
VROOM_WARN("axis typeface: no default or sans-serif typeface");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
55
74
|
vroom::set_axis_typeface(tf);
|
|
56
|
-
done = true;
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
void installJsi(jsi::Runtime& runtime) {
|
|
78
|
+
ensureAxisTypeface();
|
|
79
|
+
|
|
60
80
|
auto create = jsi::Function::createFromHostFunction(
|
|
61
81
|
runtime,
|
|
62
82
|
jsi::PropNameID::forAscii(runtime, "create"),
|
|
@@ -65,7 +85,6 @@ void installJsi(jsi::Runtime& runtime) {
|
|
|
65
85
|
const jsi::Value& /*thisVal*/,
|
|
66
86
|
const jsi::Value* /*args*/,
|
|
67
87
|
size_t /*count*/) -> jsi::Value {
|
|
68
|
-
ensureAxisTypeface(rt);
|
|
69
88
|
auto host = std::make_shared<ChartHostObject>();
|
|
70
89
|
return jsi::Object::createFromHostObject(rt, host);
|
|
71
90
|
});
|
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
#include "rsi.h"
|
|
37
37
|
#include "rsi_pane.h"
|
|
38
38
|
#include "style_inherit.h"
|
|
39
|
+
#include "tip_anchor.h"
|
|
39
40
|
#include "tip_pulse.h"
|
|
40
41
|
#include "volume.h"
|
|
41
42
|
#include "vwap.h"
|
|
@@ -311,16 +312,23 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
311
312
|
// 5.8. Line-mode tip marker — the dot (and optional pulse) at the newest
|
|
312
313
|
// close. Above the overlays so it stays the eye's anchor, but before the
|
|
313
314
|
// axis masks, which trim the ring at the price scale.
|
|
315
|
+
//
|
|
316
|
+
// Unlike every layer above, this one anchors to the newest candle in
|
|
317
|
+
// the series rather than the newest on screen, so panning into history
|
|
318
|
+
// carries the dot off the right edge with its candle (tip_anchor.h).
|
|
314
319
|
if (fade > 0.f && theme.floats[VROOM_FLOAT_LINE_TIP_DOT] > 0.5f) {
|
|
320
|
+
const auto anchor = vroom::tip_anchor::at(range.start, range.end,
|
|
321
|
+
candles.size(), morphing);
|
|
315
322
|
vroom::ma_overlay::draw_close_tip(
|
|
316
|
-
canvas, lay, bounds, visible,
|
|
317
|
-
candle_duration_ms, candle_right, candle_area_h,
|
|
323
|
+
canvas, lay, bounds, visible, anchor.slot_count, window_ms,
|
|
324
|
+
visible_start_ms, candle_duration_ms, candle_right, candle_area_h,
|
|
318
325
|
theme.colors[VROOM_COLOR_LINE],
|
|
319
326
|
theme.colors[VROOM_COLOR_BACKGROUND],
|
|
320
327
|
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade,
|
|
321
328
|
theme.floats[VROOM_FLOAT_LINE_TIP_PULSE] > 0.5f,
|
|
322
329
|
tip_pulse_elapsed_s / vroom::tip_pulse::kPeriodSeconds,
|
|
323
|
-
morph_src
|
|
330
|
+
anchor.use_morph ? morph_src : nullptr,
|
|
331
|
+
anchor.use_morph ? morph_n : 0, morph_t);
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
// 6. Axis backgrounds (mask any candle overflow). The x-axis separator
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
#pragma clang diagnostic pop
|
|
24
24
|
|
|
25
25
|
#include "labels.h"
|
|
26
|
+
#include "price_format.h"
|
|
26
27
|
#include "theme.h"
|
|
27
28
|
#include "viewport.h"
|
|
28
29
|
|
|
@@ -89,6 +90,12 @@ struct VroomChart {
|
|
|
89
90
|
// label. 0 = uncomputed; layout() falls back to a width ratio.
|
|
90
91
|
float axis_width_px = 0.f;
|
|
91
92
|
|
|
93
|
+
// How prices render, derived from the asset's own scale. Cached beside the
|
|
94
|
+
// width because the two are measured together — every label site reads this
|
|
95
|
+
// so none of them can disagree with the strip they're drawn in. Both are
|
|
96
|
+
// refreshed by labels::recompute_axis_width.
|
|
97
|
+
vroom::PriceFormat price_fmt{};
|
|
98
|
+
|
|
92
99
|
// --- price bounds (y-axis state) ---------------------------------------
|
|
93
100
|
// Two modes. Auto (price_bounds_manual == false, the default): the y-range
|
|
94
101
|
// continuously follows the visible candles each frame and `price_bounds`
|
|
@@ -23,8 +23,8 @@ sk_sp<SkPicture> render_chart_picture(VroomChart* chart);
|
|
|
23
23
|
bool is_animating(VroomChart* chart);
|
|
24
24
|
|
|
25
25
|
// Sets the typeface used for axis labels. Bridge layer calls this once at
|
|
26
|
-
// startup with a system typeface
|
|
27
|
-
//
|
|
26
|
+
// startup with a system typeface from the platform SkFontMgr. Labels are
|
|
27
|
+
// skipped if no typeface is set.
|
|
28
28
|
void set_axis_typeface(sk_sp<SkTypeface> typeface);
|
|
29
29
|
|
|
30
30
|
} // namespace vroom
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
#include "chart.h"
|
|
22
22
|
#include "fonts.h"
|
|
23
|
+
#include "price_format.h"
|
|
23
24
|
#include "theme.h"
|
|
24
25
|
#include "ticks.h"
|
|
25
26
|
|
|
@@ -146,11 +147,12 @@ void draw(SkCanvas* canvas,
|
|
|
146
147
|
// sharing the y-axis labels' column.
|
|
147
148
|
if (lay.y_axis_width_px > 0.f) {
|
|
148
149
|
const double price = vroom::y_to_price(lay, bounds, cy);
|
|
149
|
-
char buf[
|
|
150
|
-
const
|
|
150
|
+
char buf[48];
|
|
151
|
+
const vroom::PriceFormat fmt = vroom::with_tick_guard(
|
|
152
|
+
chart.price_fmt,
|
|
151
153
|
vroom::pick_price_interval(bounds.max - bounds.min,
|
|
152
154
|
vroom::price_pane_bottom(lay)));
|
|
153
|
-
vroom::format_price(buf, sizeof(buf), price,
|
|
155
|
+
vroom::format_price(buf, sizeof(buf), price, fmt);
|
|
154
156
|
const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
|
|
155
157
|
draw_badge(canvas, font, buf, axis_center_x, cy, badge_fill,
|
|
156
158
|
SK_ColorWHITE);
|
package/cpp/_core_src/fonts.h
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Global typeface for axis/label text rendering.
|
|
2
2
|
//
|
|
3
|
-
// Loaded once at startup by the bridge layer (which
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Loaded once at startup by the bridge layer (which constructs a platform
|
|
4
|
+
// SkFontMgr and grabs the system default). Held as a process-global so all
|
|
5
|
+
// charts share it; per-chart font configuration is not modeled yet.
|
|
6
6
|
|
|
7
7
|
#pragma once
|
|
8
8
|
|
package/cpp/_core_src/labels.cpp
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
#include "chart.h"
|
|
21
21
|
#include "fonts.h"
|
|
22
|
+
#include "price_format.h"
|
|
22
23
|
#include "ticks.h"
|
|
23
24
|
#include "viewport.h"
|
|
24
25
|
|
|
@@ -47,6 +48,19 @@ void advance(Fade& f, float step, float dt) {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// What the asset is worth, which is what its price precision keys off. The
|
|
52
|
+
// newest close rather than the visible range, so panning and zooming don't
|
|
53
|
+
// shift the decimals around under the user.
|
|
54
|
+
//
|
|
55
|
+
// An asset that has crossed orders of magnitude within one series (a token at
|
|
56
|
+
// 0.00001 that later trades at 100) reads its recent scale here, so deep
|
|
57
|
+
// history would round flat — the tick guard at each label site is what rescues
|
|
58
|
+
// it, since the bounds down there are tiny enough to demand more decimals.
|
|
59
|
+
double reference_price(const VroomChart& chart) {
|
|
60
|
+
if (!chart.candles.empty()) return chart.candles.back().close;
|
|
61
|
+
return (chart.price_bounds.max + chart.price_bounds.min) * 0.5;
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
// Drives a whole axis off the morph envelope instead of the per-label fades:
|
|
51
65
|
// everything in the phase's tick set shares one opacity, and anything outside it
|
|
52
66
|
// is dropped outright. At the midpoint the axis is transparent, so swapping the
|
|
@@ -153,15 +167,16 @@ void draw_y_labels(SkCanvas* canvas,
|
|
|
153
167
|
// Horizontal center of the y-axis container ([width - y_axis_width, width]).
|
|
154
168
|
// Labels (and the price box) center on this so their text shares a column.
|
|
155
169
|
const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
|
|
156
|
-
const
|
|
170
|
+
const vroom::PriceFormat fmt = vroom::with_tick_guard(
|
|
171
|
+
chart.price_fmt,
|
|
157
172
|
vroom::pick_price_interval(bounds.max - bounds.min, candle_area_h));
|
|
158
173
|
|
|
159
174
|
for (const auto& f : chart.y_fades) {
|
|
160
175
|
if (f.opacity <= 1e-3f) continue;
|
|
161
176
|
const float y = vroom::price_to_y(lay, bounds, f.price);
|
|
162
177
|
|
|
163
|
-
char buf[
|
|
164
|
-
vroom::format_price(buf, sizeof(buf), f.price,
|
|
178
|
+
char buf[48];
|
|
179
|
+
vroom::format_price(buf, sizeof(buf), f.price, fmt);
|
|
165
180
|
const size_t len = std::strlen(buf);
|
|
166
181
|
const float text_w = font.measureText(
|
|
167
182
|
buf, len, SkTextEncoding::kUTF8);
|
|
@@ -347,6 +362,10 @@ void gc_x_fades(VroomChart& chart) {
|
|
|
347
362
|
// ----- Axis-width sizing ----------------------------------------------------
|
|
348
363
|
|
|
349
364
|
void recompute_axis_width(VroomChart& chart) {
|
|
365
|
+
// Precision first, and unconditionally: the label sites read it whether or
|
|
366
|
+
// not a typeface has loaded, and it's what the measurement below sizes for.
|
|
367
|
+
chart.price_fmt = vroom::price_format_for(reference_price(chart));
|
|
368
|
+
|
|
350
369
|
auto tf = vroom::axis_typeface();
|
|
351
370
|
if (!tf) {
|
|
352
371
|
chart.axis_width_px = 0.f;
|
|
@@ -370,11 +389,16 @@ void recompute_axis_width(VroomChart& chart) {
|
|
|
370
389
|
|
|
371
390
|
SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
|
|
372
391
|
const auto lay = chart.layout();
|
|
373
|
-
|
|
392
|
+
// Same guard the label sites apply, against the bounds this is sizing for,
|
|
393
|
+
// so a zoom deep enough to add decimals widens the strip to hold them.
|
|
394
|
+
const vroom::PriceFormat fmt = vroom::with_tick_guard(
|
|
395
|
+
chart.price_fmt,
|
|
374
396
|
vroom::pick_price_interval(hi - lo, vroom::price_pane_bottom(lay)));
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
397
|
+
// At the same decimals the longest label is whichever bound has the most
|
|
398
|
+
// integer digits, separators included.
|
|
399
|
+
char buf_hi[48], buf_lo[48];
|
|
400
|
+
vroom::format_price(buf_hi, sizeof(buf_hi), hi, fmt);
|
|
401
|
+
vroom::format_price(buf_lo, sizeof(buf_lo), lo, fmt);
|
|
378
402
|
const float w_hi = font.measureText(
|
|
379
403
|
buf_hi, std::strlen(buf_hi), SkTextEncoding::kUTF8);
|
|
380
404
|
const float w_lo = font.measureText(
|
|
@@ -108,6 +108,11 @@ void draw_close_gradient(SkCanvas* canvas,
|
|
|
108
108
|
// switch. `opacity` scales everything, fading the marker in with the line during
|
|
109
109
|
// the candle→line morph.
|
|
110
110
|
//
|
|
111
|
+
// `n` counts the slots to the candle being marked, which is the series' newest
|
|
112
|
+
// rather than the visible slice's — see tip_anchor.h, which derives it. That
|
|
113
|
+
// candle can sit off the pane when panned into history, and the marker then
|
|
114
|
+
// draws nothing.
|
|
115
|
+
//
|
|
111
116
|
// `pulse_phase` is in cycles and wraps, so the caller can hand over elapsed time
|
|
112
117
|
// divided by tip_pulse::kPeriodSeconds. Ignored unless `pulse`.
|
|
113
118
|
void draw_close_tip(SkCanvas* canvas,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#include "price_format.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstdio>
|
|
6
|
+
|
|
7
|
+
namespace vroom {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
// Room for %.*f of any finite double at kPriceMaxDecimals: sign, the 309
|
|
12
|
+
// integer digits DBL_MAX can reach, the point, and the decimals.
|
|
13
|
+
constexpr size_t kPlainCap = 340;
|
|
14
|
+
|
|
15
|
+
constexpr char kGroupSeparator = ',';
|
|
16
|
+
constexpr int kGroupSize = 3;
|
|
17
|
+
|
|
18
|
+
} // namespace
|
|
19
|
+
|
|
20
|
+
int significant_decimals(double reference) {
|
|
21
|
+
const double r = std::fabs(reference);
|
|
22
|
+
if (!(r > 0.0) || !std::isfinite(r)) return kPriceMinDecimals;
|
|
23
|
+
// floor(log10) is the exponent of the leading digit, so subtracting it from
|
|
24
|
+
// the digit budget lands the last significant digit on the final decimal.
|
|
25
|
+
const int exponent = static_cast<int>(std::floor(std::log10(r)));
|
|
26
|
+
return std::clamp(kPriceSigDigits - 1 - exponent, kPriceMinDecimals,
|
|
27
|
+
kPriceMaxDecimals);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
PriceFormat price_format_for(double reference) {
|
|
31
|
+
return PriceFormat{significant_decimals(reference), true};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
int price_decimals(double interval) {
|
|
35
|
+
if (!(interval > 0.0) || !std::isfinite(interval)) return kPriceMinDecimals;
|
|
36
|
+
const double d = std::ceil(-std::log10(interval) - 1e-12);
|
|
37
|
+
if (d < 0.0) return 0;
|
|
38
|
+
if (d > static_cast<double>(kPriceMaxDecimals)) return kPriceMaxDecimals;
|
|
39
|
+
return static_cast<int>(d);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
PriceFormat with_tick_guard(const PriceFormat& fmt, double interval) {
|
|
43
|
+
return PriceFormat{std::max(fmt.decimals, price_decimals(interval)),
|
|
44
|
+
fmt.group};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void format_price(char* buf, size_t buf_size, double price,
|
|
48
|
+
const PriceFormat& fmt) {
|
|
49
|
+
if (!buf || buf_size == 0) return;
|
|
50
|
+
const int decimals = std::clamp(fmt.decimals, 0, kPriceMaxDecimals);
|
|
51
|
+
|
|
52
|
+
char plain[kPlainCap];
|
|
53
|
+
if (std::snprintf(plain, sizeof(plain), "%.*f", decimals, price) < 0) {
|
|
54
|
+
buf[0] = '\0';
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!fmt.group) {
|
|
58
|
+
std::snprintf(buf, buf_size, "%s", plain);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const char* src = plain;
|
|
63
|
+
size_t out = 0;
|
|
64
|
+
// Leaves room for the terminator on every write, so the result truncates
|
|
65
|
+
// the way snprintf would rather than running off the end.
|
|
66
|
+
const auto put = [&](char c) {
|
|
67
|
+
if (out + 1 < buf_size) buf[out++] = c;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (*src == '-' || *src == '+') put(*src++);
|
|
71
|
+
|
|
72
|
+
// Digits up to the point, or the whole run when decimals == 0. A non-finite
|
|
73
|
+
// price has none, and falls through to the tail copy as "inf" / "nan".
|
|
74
|
+
const char* int_end = src;
|
|
75
|
+
while (*int_end >= '0' && *int_end <= '9') ++int_end;
|
|
76
|
+
|
|
77
|
+
const ptrdiff_t digits = int_end - src;
|
|
78
|
+
for (ptrdiff_t i = 0; i < digits; ++i) {
|
|
79
|
+
if (i > 0 && (digits - i) % kGroupSize == 0) put(kGroupSeparator);
|
|
80
|
+
put(src[i]);
|
|
81
|
+
}
|
|
82
|
+
for (const char* p = int_end; *p != '\0'; ++p) put(*p);
|
|
83
|
+
|
|
84
|
+
buf[out] = '\0';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} // namespace vroom
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// How a price is turned into the string on the y-axis.
|
|
2
|
+
//
|
|
3
|
+
// Precision follows the asset's own scale rather than the tick interval: a
|
|
4
|
+
// sub-cent token needs nine decimals to say anything at all, while a
|
|
5
|
+
// five-figure one is unreadable past two. Deriving it from the interval — as
|
|
6
|
+
// the axis used to — gets both wrong, printing "84000" for the one and
|
|
7
|
+
// "0.000060" for the other.
|
|
8
|
+
//
|
|
9
|
+
// The rule is a fixed count of significant digits with a floor, which is what
|
|
10
|
+
// reproduces a conventional price scale across the whole range:
|
|
11
|
+
//
|
|
12
|
+
// 4.4094e-5 -> 9 decimals 0.000044094
|
|
13
|
+
// 0.023397 -> 6 decimals 0.023397
|
|
14
|
+
// 80285.20 -> 2 decimals 80,285.20 (floor)
|
|
15
|
+
// 2513.92 -> 2 decimals 2,513.92 (floor)
|
|
16
|
+
//
|
|
17
|
+
// The reference is the asset's own price, not the visible range, so the
|
|
18
|
+
// precision holds steady while the user pans and zooms. A range-derived count
|
|
19
|
+
// would change under the user's finger and drag the axis width along with it.
|
|
20
|
+
//
|
|
21
|
+
// Skia-free and separately compiled so the unit tests can cover it; see
|
|
22
|
+
// tests/test_price_format.cpp.
|
|
23
|
+
|
|
24
|
+
#pragma once
|
|
25
|
+
|
|
26
|
+
#include <cstddef>
|
|
27
|
+
|
|
28
|
+
namespace vroom {
|
|
29
|
+
|
|
30
|
+
// Significant digits a price is quoted to. Five is what conventional price
|
|
31
|
+
// scales show: it keeps 0.023397 intact and still fits 0.000044094.
|
|
32
|
+
inline constexpr int kPriceSigDigits = 5;
|
|
33
|
+
|
|
34
|
+
// Prices at or above ~1 unit read as currency, where fewer than two decimals
|
|
35
|
+
// looks broken (a "$84,000" axis next to a "$80,285.20" last price).
|
|
36
|
+
inline constexpr int kPriceMinDecimals = 2;
|
|
37
|
+
|
|
38
|
+
// Past this the digits are noise, and %.*f starts printing the binary
|
|
39
|
+
// representation's tail rather than anything the feed meant.
|
|
40
|
+
inline constexpr int kPriceMaxDecimals = 12;
|
|
41
|
+
|
|
42
|
+
// How to render a price. `group` inserts thousands separators, which only ever
|
|
43
|
+
// affects the integer part.
|
|
44
|
+
struct PriceFormat {
|
|
45
|
+
int decimals = kPriceMinDecimals;
|
|
46
|
+
bool group = true;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Decimals giving kPriceSigDigits significant digits for an asset priced around
|
|
50
|
+
// |reference|, clamped to [kPriceMinDecimals, kPriceMaxDecimals]. A zero or
|
|
51
|
+
// non-finite reference falls back to the floor.
|
|
52
|
+
int significant_decimals(double reference);
|
|
53
|
+
|
|
54
|
+
// The format for an asset priced around `reference`.
|
|
55
|
+
PriceFormat price_format_for(double reference);
|
|
56
|
+
|
|
57
|
+
// Decimal places needed so adjacent ticks `interval` apart don't collapse to
|
|
58
|
+
// the same string. 0.01 → 2, 0.005 → 3, 1e-8 → 8. Clamped to [0, 12].
|
|
59
|
+
int price_decimals(double interval);
|
|
60
|
+
|
|
61
|
+
// `fmt` raised, if need be, so ticks `interval` apart stay distinct. The asset's
|
|
62
|
+
// precision is the floor; only a zoom deeper than that precision adds to it.
|
|
63
|
+
PriceFormat with_tick_guard(const PriceFormat& fmt, double interval);
|
|
64
|
+
|
|
65
|
+
// Writes `price` into `buf` per `fmt`, truncating rather than overrunning.
|
|
66
|
+
void format_price(char* buf, size_t buf_size, double price,
|
|
67
|
+
const PriceFormat& fmt);
|
|
68
|
+
|
|
69
|
+
} // namespace vroom
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
#include "chart.h"
|
|
20
20
|
#include "fonts.h"
|
|
21
|
+
#include "price_format.h"
|
|
21
22
|
#include "theme.h"
|
|
22
23
|
#include "ticks.h"
|
|
23
24
|
#include "viewport.h"
|
|
@@ -66,10 +67,11 @@ void draw(SkCanvas* canvas,
|
|
|
66
67
|
font.setSubpixel(true);
|
|
67
68
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
68
69
|
|
|
69
|
-
char buf[
|
|
70
|
-
const
|
|
70
|
+
char buf[48];
|
|
71
|
+
const vroom::PriceFormat fmt = vroom::with_tick_guard(
|
|
72
|
+
chart.price_fmt,
|
|
71
73
|
vroom::pick_price_interval(bounds.max - bounds.min, candle_area_h));
|
|
72
|
-
vroom::format_price(buf, sizeof(buf), last.close,
|
|
74
|
+
vroom::format_price(buf, sizeof(buf), last.close, fmt);
|
|
73
75
|
const size_t len = std::strlen(buf);
|
|
74
76
|
|
|
75
77
|
// Measure the tight glyph bounds (origin at the baseline) so we can center
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
#include "chart.h"
|
|
23
23
|
#include "fonts.h"
|
|
24
|
+
#include "price_format.h"
|
|
24
25
|
#include "price_line_layout.h"
|
|
25
26
|
#include "theme.h"
|
|
26
27
|
#include "ticks.h"
|
|
@@ -214,11 +215,11 @@ void draw_axis_badge(SkCanvas* canvas,
|
|
|
214
215
|
double price,
|
|
215
216
|
float y,
|
|
216
217
|
SkColor fill,
|
|
217
|
-
|
|
218
|
+
const PriceFormat& fmt) {
|
|
218
219
|
if (lay.y_axis_width_px <= 0.f) return;
|
|
219
220
|
|
|
220
|
-
char buf[
|
|
221
|
-
vroom::format_price(buf, sizeof(buf), price,
|
|
221
|
+
char buf[48];
|
|
222
|
+
vroom::format_price(buf, sizeof(buf), price, fmt);
|
|
222
223
|
const size_t len = std::strlen(buf);
|
|
223
224
|
|
|
224
225
|
SkRect tb;
|
|
@@ -255,7 +256,8 @@ void draw(SkCanvas* canvas,
|
|
|
255
256
|
const VroomPriceLineStyle& style = chart.price_line_style;
|
|
256
257
|
SkFont font;
|
|
257
258
|
const bool has_font = label_font(chart, &font);
|
|
258
|
-
const
|
|
259
|
+
const vroom::PriceFormat fmt = vroom::with_tick_guard(
|
|
260
|
+
chart.price_fmt,
|
|
259
261
|
vroom::pick_price_interval(bounds.max - bounds.min, candle_area_h));
|
|
260
262
|
|
|
261
263
|
for (size_t i = 0; i < chart.price_lines.size(); ++i) {
|
|
@@ -355,7 +357,7 @@ void draw(SkCanvas* canvas,
|
|
|
355
357
|
|
|
356
358
|
if (has_font && (pl.flags & VROOM_PRICE_LINE_AXIS_LABEL) != 0) {
|
|
357
359
|
draw_axis_badge(canvas, font, lay, render_price(chart, i), y,
|
|
358
|
-
line_color,
|
|
360
|
+
line_color, fmt);
|
|
359
361
|
}
|
|
360
362
|
}
|
|
361
363
|
}
|
package/cpp/_core_src/ticks.cpp
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
#include <algorithm>
|
|
4
4
|
#include <cmath>
|
|
5
|
-
#include <cstdio>
|
|
6
5
|
#include <ctime>
|
|
7
6
|
|
|
8
7
|
namespace vroom {
|
|
@@ -146,17 +145,4 @@ double pick_price_interval(double range, float candle_area_h) {
|
|
|
146
145
|
return nice * magnitude;
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
int price_decimals(double interval) {
|
|
150
|
-
if (!(interval > 0.0) || !std::isfinite(interval)) return 2;
|
|
151
|
-
const double d = std::ceil(-std::log10(interval) - 1e-12);
|
|
152
|
-
if (d < 0.0) return 0;
|
|
153
|
-
if (d > 12.0) return 12;
|
|
154
|
-
return static_cast<int>(d);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
void format_price(char* buf, size_t buf_size, double price, int decimals) {
|
|
158
|
-
if (!buf || buf_size == 0) return;
|
|
159
|
-
std::snprintf(buf, buf_size, "%.*f", decimals, price);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
148
|
} // namespace vroom
|
package/cpp/_core_src/ticks.h
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
#pragma once
|
|
8
8
|
|
|
9
|
-
#include <cstddef>
|
|
10
9
|
#include <cstdint>
|
|
11
10
|
|
|
12
11
|
namespace vroom {
|
|
@@ -49,13 +48,8 @@ int64_t next_tick(int64_t t, const TimeTick& tick);
|
|
|
49
48
|
// "Nice number" tick selection for the y-axis: snaps to 1, 2, or 5 × 10ⁿ
|
|
50
49
|
// based on the price range and target spacing. Returns the price interval
|
|
51
50
|
// in the same units as the data (e.g. dollars).
|
|
51
|
+
//
|
|
52
|
+
// Turning that interval into a label string is price_format.h's job.
|
|
52
53
|
double pick_price_interval(double range, float candle_area_h);
|
|
53
54
|
|
|
54
|
-
// Decimal places needed so adjacent ticks `interval` apart don't collapse to
|
|
55
|
-
// the same string. 0.01 → 2, 0.005 → 3, 1e-8 → 8. Clamped to [0, 12].
|
|
56
|
-
int price_decimals(double interval);
|
|
57
|
-
|
|
58
|
-
// Writes `price` into `buf` using `decimals` (from price_decimals).
|
|
59
|
-
void format_price(char* buf, size_t buf_size, double price, int decimals);
|
|
60
|
-
|
|
61
55
|
} // namespace vroom
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Which slot the line chart's tip marker anchors to (see draw_close_tip in
|
|
2
|
+
// ma_overlay.cpp).
|
|
3
|
+
//
|
|
4
|
+
// The tip marks the newest close in the *series*, not the newest close on
|
|
5
|
+
// screen — the same rule the price indicator follows (price_indicator.cpp).
|
|
6
|
+
// Panning into history therefore carries the dot off the right edge with the
|
|
7
|
+
// candle it belongs to, where draw_close_tip's on-pane guard drops it.
|
|
8
|
+
//
|
|
9
|
+
// Every other line-chart layer draws the visible slice, so the tip is the one
|
|
10
|
+
// caller that has to reach past range.end. The slice is a view into the same
|
|
11
|
+
// contiguous candle buffer, so widening the slot count is enough to reach the
|
|
12
|
+
// newest candle — no second pointer.
|
|
13
|
+
//
|
|
14
|
+
// Skia-free and header-only so the unit tests can cover it; see
|
|
15
|
+
// tests/test_tip_anchor.cpp.
|
|
16
|
+
|
|
17
|
+
#pragma once
|
|
18
|
+
|
|
19
|
+
#include <cstddef>
|
|
20
|
+
|
|
21
|
+
namespace vroom::tip_anchor {
|
|
22
|
+
|
|
23
|
+
// How draw_close_tip should be called for one frame.
|
|
24
|
+
struct Anchor {
|
|
25
|
+
// Slot count to pass in place of the visible count, so slot 0 resolves to
|
|
26
|
+
// the newest candle in the series. Never 0 for a non-empty visible slice.
|
|
27
|
+
std::size_t slot_count;
|
|
28
|
+
// Whether the interval-morph capture still lines up with slot 0.
|
|
29
|
+
bool use_morph;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// `range_start`/`range_end` are the visible slice's half-open bounds into a
|
|
33
|
+
// series of `total` candles; `morphing` is whether an interval morph is running.
|
|
34
|
+
//
|
|
35
|
+
// The capture taken at a timeframe switch is indexed from the newest *visible*
|
|
36
|
+
// candle, so it only pairs with slot 0 while the newest candle is the one at the
|
|
37
|
+
// right edge. Scrolled back, pairing them would interpolate the tip between two
|
|
38
|
+
// unrelated candles and strand the dot mid-pane for the length of the switch.
|
|
39
|
+
inline Anchor at(std::size_t range_start,
|
|
40
|
+
std::size_t range_end,
|
|
41
|
+
std::size_t total,
|
|
42
|
+
bool morphing) {
|
|
43
|
+
// A caller with an empty or out-of-bounds slice has nothing to anchor to;
|
|
44
|
+
// reporting 0 slots lets draw_close_tip take its existing early-out.
|
|
45
|
+
if (range_start >= total) return Anchor{0, false};
|
|
46
|
+
return Anchor{total - range_start, morphing && range_end >= total};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
} // namespace vroom::tip_anchor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-vroom-chart",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Mobile-first Skia candlestick chart for React Native",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Darion Welch",
|
|
@@ -59,20 +59,21 @@
|
|
|
59
59
|
"access": "public"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@shopify/react-native-skia": ">=2.
|
|
62
|
+
"@shopify/react-native-skia": ">=2.11.0",
|
|
63
63
|
"react": "*",
|
|
64
64
|
"react-native": "*",
|
|
65
65
|
"react-native-gesture-handler": ">=2.16.0",
|
|
66
|
-
"react-native-reanimated": ">=4.0.0"
|
|
66
|
+
"react-native-reanimated": ">=4.0.0",
|
|
67
|
+
"react-native-worklets": ">=0.7.0"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
69
|
-
"@shopify/react-native-skia": "2.
|
|
70
|
+
"@shopify/react-native-skia": "2.11.0",
|
|
70
71
|
"@types/react": "~19.1.0",
|
|
71
72
|
"react": "19.1.0",
|
|
72
73
|
"react-native": "0.81.5",
|
|
73
74
|
"react-native-gesture-handler": "~2.28.0",
|
|
74
75
|
"react-native-reanimated": "~4.1.7",
|
|
75
|
-
"react-native-worklets": "0.
|
|
76
|
+
"react-native-worklets": "0.7.4",
|
|
76
77
|
"tsup": "^8.5.1",
|
|
77
78
|
"typescript": "~5.9.2",
|
|
78
79
|
"vitest": "^3.2.6",
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
"typecheck": "tsc --noEmit",
|
|
83
84
|
"test": "vitest run",
|
|
84
85
|
"build": "tsup",
|
|
86
|
+
"check:podspec": "ruby scripts/check-podspec-portable.rb",
|
|
85
87
|
"vendor:core": "node scripts/vendor-core.mjs"
|
|
86
88
|
}
|
|
87
89
|
}
|
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
require "json"
|
|
2
|
+
require "pathname"
|
|
2
3
|
|
|
3
4
|
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
5
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# Locate the @shopify/react-native-skia source. We need it because RN-Skia ships
|
|
7
|
+
# its Skia headers via `#include "include/core/SkPicture.h"`, which only resolves
|
|
8
|
+
# when the include path points at the source `cpp/` and `cpp/skia/` directories —
|
|
9
|
+
# CocoaPods' flat Headers/Public/ layout loses the `include/...` prefix.
|
|
10
|
+
#
|
|
11
|
+
# The paths must be emitted relative to $(PODS_TARGET_SRCROOT), never absolute.
|
|
12
|
+
# CocoaPods serializes this spec to `Pods/Local Podspecs/*.podspec.json` and hashes
|
|
13
|
+
# that file into Podfile.lock's SPEC CHECKSUMS, so an absolute path bakes the
|
|
14
|
+
# checkout root of whoever ran `pod install` into the lockfile and every other
|
|
15
|
+
# machine fails `pod install --deployment`. Resolving at spec-eval time (rather
|
|
16
|
+
# than hardcoding `../@shopify/react-native-skia`) keeps this correct under
|
|
17
|
+
# non-hoisted layouts such as pnpm's.
|
|
9
18
|
skia_pkg_json = `node --print "require.resolve('@shopify/react-native-skia/package.json')"`.strip
|
|
19
|
+
raise "react-native-vroom-chart: could not resolve @shopify/react-native-skia" if skia_pkg_json.empty?
|
|
10
20
|
skia_src_dir = File.dirname(skia_pkg_json)
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
skia_rel_dir = File.join("$(PODS_TARGET_SRCROOT)",
|
|
22
|
+
Pathname.new(skia_src_dir).relative_path_from(Pathname.new(__dir__)).to_s)
|
|
23
|
+
skia_cpp_dir = File.join(skia_rel_dir, "cpp")
|
|
24
|
+
skia_skia_dir = File.join(skia_rel_dir, "cpp", "skia")
|
|
25
|
+
skia_api_dir = File.join(skia_rel_dir, "cpp", "api")
|
|
26
|
+
# Absolute is fine here: the result only selects a preprocessor-define string,
|
|
27
|
+
# so it never reaches the emitted spec.
|
|
28
|
+
use_graphite = File.exist?(File.join(skia_src_dir, "libs", ".graphite"))
|
|
29
|
+
skia_preprocessor_defs = use_graphite ?
|
|
30
|
+
"$(inherited) SK_GRAPHITE=1 SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API=1 SK_DISABLE_LEGACY_SHAPER_FACTORY=1" :
|
|
31
|
+
"$(inherited) SK_METAL=1 SK_GANESH=1 SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API=1 SK_DISABLE_LEGACY_SHAPER_FACTORY=1"
|
|
13
32
|
|
|
14
33
|
Pod::Spec.new do |s|
|
|
15
34
|
s.name = "react-native-vroom-chart"
|
|
@@ -60,12 +79,13 @@ Pod::Spec.new do |s|
|
|
|
60
79
|
'"$(PODS_TARGET_SRCROOT)/cpp"',
|
|
61
80
|
"\"#{skia_cpp_dir}\"",
|
|
62
81
|
"\"#{skia_skia_dir}\"",
|
|
82
|
+
"\"#{skia_api_dir}\"",
|
|
63
83
|
].join(" "),
|
|
64
|
-
"CLANG_CXX_LANGUAGE_STANDARD" => "c++
|
|
65
|
-
# Match RN-Skia's Skia build flags
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
|
|
85
|
+
# Match RN-Skia's Skia build flags (Ganesh vs Graphite, plus the
|
|
86
|
+
# SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API define both branches set) so our
|
|
87
|
+
# SkCanvas/SkPicture code sees the same ABI and doesn't mis-link.
|
|
88
|
+
"GCC_PREPROCESSOR_DEFINITIONS" => skia_preprocessor_defs,
|
|
69
89
|
}
|
|
70
90
|
|
|
71
91
|
s.dependency "React-Core"
|
package/cpp/VroomSkiaContext.cpp
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#include "VroomSkiaContext.h"
|
|
2
|
-
|
|
3
|
-
namespace vroom {
|
|
4
|
-
|
|
5
|
-
namespace jsi = facebook::jsi;
|
|
6
|
-
|
|
7
|
-
std::shared_ptr<RNSkia::RNSkPlatformContext> getRNSkContext(
|
|
8
|
-
jsi::Runtime& runtime) {
|
|
9
|
-
auto val = runtime.global().getProperty(runtime, "SkiaApi");
|
|
10
|
-
if (!val.isObject()) return nullptr;
|
|
11
|
-
auto obj = val.asObject(runtime);
|
|
12
|
-
if (!obj.isHostObject(runtime)) return nullptr;
|
|
13
|
-
auto host = obj.asHostObject(runtime);
|
|
14
|
-
|
|
15
|
-
// `global.SkiaApi` is always installed by RN-Skia's own
|
|
16
|
-
// RNSkManager::installBindings() as exactly a `JsiSkApi` host object — never
|
|
17
|
-
// any other JsiSkHostObject subclass — so this cast is safe as a plain
|
|
18
|
-
// static_cast, without any RTTI check. We deliberately avoid
|
|
19
|
-
// dynamic_pointer_cast here: on Android, RN-Skia is compiled into its own
|
|
20
|
-
// librnskia.so, separate from libvroomchart.so, so a `JsiSkApi` constructed
|
|
21
|
-
// by librnskia.so's code carries typeinfo from that .so; a dynamic_cast
|
|
22
|
-
// performed here (in libvroomchart.so) sees an unmerged RTTI record for
|
|
23
|
-
// "the same" class and always fails, even though the object is perfectly
|
|
24
|
-
// valid to use. (Same underlying issue as the JsiSkPicture cross-.so cast
|
|
25
|
-
// documented in VroomChartHostObject.cpp — but there RN-Skia's own compiled
|
|
26
|
-
// code does the cast on an object *we* construct, so we can't avoid it;
|
|
27
|
-
// here *we* do the cast on an object *they* construct, so we can just skip
|
|
28
|
-
// the runtime check we don't need.)
|
|
29
|
-
auto* accessor = static_cast<HostObjectAccessor*>(host.get());
|
|
30
|
-
return accessor->getContext();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
} // namespace vroom
|
package/cpp/VroomSkiaContext.h
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// Helpers for reaching into RN-Skia's installed JSI bindings to grab its
|
|
2
|
-
// RNSkPlatformContext — which we need so we can use JsiSkFontMgrFactory to
|
|
3
|
-
// get the system font manager cross-platform.
|
|
4
|
-
//
|
|
5
|
-
// JsiSkHostObject::getContext() is protected, so we use a "friend helper"
|
|
6
|
-
// subclass that exposes it via `using`. We never construct an instance —
|
|
7
|
-
// we just static_cast existing host objects to the accessor type.
|
|
8
|
-
|
|
9
|
-
#pragma once
|
|
10
|
-
|
|
11
|
-
#include <memory>
|
|
12
|
-
|
|
13
|
-
#include <jsi/jsi.h>
|
|
14
|
-
|
|
15
|
-
#include "JsiSkHostObjects.h" // RNSkia::JsiSkHostObject
|
|
16
|
-
#include "RNSkPlatformContext.h" // RNSkia::RNSkPlatformContext
|
|
17
|
-
|
|
18
|
-
namespace vroom {
|
|
19
|
-
|
|
20
|
-
class HostObjectAccessor : public RNSkia::JsiSkHostObject {
|
|
21
|
-
public:
|
|
22
|
-
using RNSkia::JsiSkHostObject::getContext;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Returns the RN-Skia platform context by reading global.SkiaApi.
|
|
26
|
-
// Returns nullptr if SkiaApi isn't on the global yet (RN-Skia not installed).
|
|
27
|
-
std::shared_ptr<RNSkia::RNSkPlatformContext> getRNSkContext(
|
|
28
|
-
facebook::jsi::Runtime& runtime);
|
|
29
|
-
|
|
30
|
-
} // namespace vroom
|