Skip to content

Commit

Permalink
TextBlob::getGlyphs, ::getPositions, ::getClusters
Browse files Browse the repository at this point in the history
  • Loading branch information
tonsky committed Dec 8, 2020
1 parent 251149c commit c6d3e65
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ private void loop() {
scenes.put("Text Style", new TextStyleScene());
scenes.put("Wall of Text", new WallOfTextScene());
scenes.put("Watches", new WatchesScene());
currentScene = "Wall of Text";
currentScene = "RunHandler";
t0 = System.nanoTime();

while (!glfwWindowShouldClose(window)) {
Expand Down
119 changes: 119 additions & 0 deletions native/src/TextBlob.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <cstring>
#include <iostream>
#include <jni.h>
#include "SkData.h"
Expand Down Expand Up @@ -96,3 +97,121 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skija_TextBlob__1nMakeFrom
SkTextBlob* instance = SkTextBlob::Deserialize(data->data(), data->size(), {}).release();
return reinterpret_cast<jlong>(instance);
}

// Must match SkTextBlobPriv.h
class RunRecordClone {
public:
SkFont fFont;
uint32_t fCount;
SkPoint fOffset;
uint32_t fFlags;

uint16_t* glyphBuffer() const {
// Glyphs are stored immediately following the record.
return reinterpret_cast<uint16_t*>(const_cast<RunRecordClone*>(this) + 1);
}

SkScalar* posBuffer() const {
// Position scalars follow the (aligned) glyph buffer.
return reinterpret_cast<SkScalar*>(reinterpret_cast<uint8_t*>(this->glyphBuffer()) +
SkAlign4(fCount * sizeof(uint16_t)));
}

uint32_t* textSizePtr() const {
// textSize follows the position buffer.
return (uint32_t*)(&this->posBuffer()[fCount * ScalarsPerGlyph(positioning())]);
}

uint32_t textSize() const {
return isExtended() ? *this->textSizePtr() : 0;
}

uint32_t* clusterBuffer() const {
// clusters follow the textSize.
return isExtended() ? 1 + this->textSizePtr() : nullptr;
}

char* textBuffer() const {
return isExtended()
? reinterpret_cast<char*>(this->clusterBuffer() + fCount)
: nullptr;
}

uint8_t positioning() const {
return fFlags & 0x3; // kPositioning_Mask
}

bool isExtended() const {
return fFlags & 0x8; // kExtended_Flag
}

static unsigned ScalarsPerGlyph(uint8_t pos) {
const uint8_t gScalarsPerPositioning[] = {
0, // kDefault_Positioning
1, // kHorizontal_Positioning
2, // kFull_Positioning
4, // kRSXform_Positioning
};
return gScalarsPerPositioning[pos];
}
};

extern "C" JNIEXPORT jshortArray JNICALL Java_org_jetbrains_skija_TextBlob__1nGetGlyphs
(JNIEnv* env, jclass jclass, jlong ptr) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
std::vector<jshort> glyphs;
size_t stored = 0;
while (iter.next(&run)) {
glyphs.resize(stored + run.fGlyphCount);
memcpy(&glyphs[stored], run.fGlyphIndices, run.fGlyphCount * sizeof(uint16_t));
stored += run.fGlyphCount;
}
return javaShortArray(env, glyphs);
}

extern "C" JNIEXPORT jfloatArray JNICALL Java_org_jetbrains_skija_TextBlob__1nGetPositions
(JNIEnv* env, jclass jclass, jlong ptr) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
std::vector<jfloat> positions;
size_t stored = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
unsigned scalarsPerGlyph = RunRecordClone::ScalarsPerGlyph(runRecord->positioning());
positions.resize(stored + run.fGlyphCount * scalarsPerGlyph);
memcpy(&positions[stored], runRecord->posBuffer(), run.fGlyphCount * scalarsPerGlyph * sizeof(SkScalar));
stored += run.fGlyphCount * scalarsPerGlyph;
}
return javaFloatArray(env, positions);
}

extern "C" JNIEXPORT jintArray JNICALL Java_org_jetbrains_skija_TextBlob__1nGetClusters
(JNIEnv* env, jclass jclass, jlong ptr) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
std::vector<jint> clusters;
size_t stored = 0;
// uint32_t cluster8 = 0;
uint32_t runStart16 = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (!runRecord->isExtended())
return nullptr;

skija::UtfIndicesConverter conv(runRecord->textBuffer(), runRecord->textSize());
clusters.resize(stored + run.fGlyphCount);
for (int i = 0; i < run.fGlyphCount; ++i)
clusters[stored + i] = conv.from8To16(runRecord->clusterBuffer()[i]) + runStart16;
runStart16 += conv.from8To16(runRecord->textSize());
// memcpy(&clusters[stored], runRecord->clusterBuffer(), run.fGlyphCount * sizeof(uint32_t));

stored += run.fGlyphCount;
}
return javaIntArray(env, clusters);
}
10 changes: 10 additions & 0 deletions shared/src/main/java/org/jetbrains/skija/Point.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ public class Point {
return arr;
}

@Contract("null -> null; !null -> new")
public static @Nullable Point[] fromArray(@Nullable float[] pts) {
if (pts == null) return null;
assert pts.length % 2 == 0 : "Expected " + pts.length + " % 2 == 0";
Point[] arr = new Point[pts.length / 2];
for (int i = 0; i < pts.length / 2; ++i)
arr[i] = new Point(pts[i * 2], pts[i * 2 + 1]);
return arr;
}

public Point offset(float dx, float dy) {
return new Point(_x + dx, _y + dy);
}
Expand Down
44 changes: 44 additions & 0 deletions shared/src/main/java/org/jetbrains/skija/TextBlob.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,47 @@ public static TextBlob makeFromData(Data data) {
return ptr == 0 ? null : new TextBlob(ptr);
}

/**
* @return glyph indices for the whole blob
*/
@NotNull
public short[] getGlyphs() {
Stats.onNativeCall();
return _nGetGlyphs(_ptr);
}

/**
* <p>Return result depends on how blob was constructed.</p>
*
* <ul><li>makeFromPosH returns 1 float per glyph (x pos)
* <li>makeFromPos returns 2 floats per glyph (x, y pos)
* <li>makeFromRSXform returns 4 floats per glyph
* </ul>
*
* <p>Blobs constructed by TextBlobBuilderRunHandler/Shaper default handler have 2 floats per glyph.</p>
*
* @return glyph positions for the blob if it was made with makeFromPos, null otherwise
*/
@NotNull
public float[] getPositions() {
Stats.onNativeCall();
return _nGetPositions(_ptr);
}

/**
* Only works on TextBlobs that come from TextBlobBuilderRunHandler/Shaper default handler.
*
* @return utf-16 offsets of clusters that start the glyph
* @throws IllegalArgumentException if TextBlob doesn’t have this information
*/
@NotNull
public int[] getClusters() {
Stats.onNativeCall();
int[] res = _nGetClusters(_ptr);
if (res == null)
throw new IllegalArgumentException();
return res;
}

@ApiStatus.Internal
public static class _FinalizerHolder {
Expand All @@ -131,4 +172,7 @@ public static class _FinalizerHolder {
@ApiStatus.Internal public static native long _nMakeFromRSXform(short[] glyphs, float[] xform, long fontPtr);
@ApiStatus.Internal public static native long _nSerializeToData(long ptr /*, SkSerialProcs */);
@ApiStatus.Internal public static native long _nMakeFromData(long dataPtr /*, SkDeserialProcs */);
@ApiStatus.Internal public static native short[] _nGetGlyphs(long ptr);
@ApiStatus.Internal public static native float[] _nGetPositions(long ptr);
@ApiStatus.Internal public static native int[] _nGetClusters(long ptr);
}

0 comments on commit c6d3e65

Please sign in to comment.