Merge pull request #3121 from harfbuzz/subset-args
Fix up `hb-subset --help-all`
This commit is contained in:
commit
8940409e3c
|
@ -1,38 +1,48 @@
|
||||||
HB_VIEW_sources = \
|
HB_VIEW_sources = \
|
||||||
hb-view.cc \
|
|
||||||
options.cc \
|
|
||||||
options.hh \
|
|
||||||
main-font-text.hh \
|
|
||||||
shape-consumer.hh \
|
|
||||||
ansi-print.cc \
|
|
||||||
ansi-print.hh \
|
ansi-print.hh \
|
||||||
helper-cairo.cc \
|
face-options.hh \
|
||||||
helper-cairo.hh \
|
font-options.hh \
|
||||||
helper-cairo-ansi.cc \
|
hb-view.cc \
|
||||||
helper-cairo-ansi.hh \
|
helper-cairo-ansi.hh \
|
||||||
view-cairo.cc \
|
helper-cairo.hh \
|
||||||
|
main-font-text.hh \
|
||||||
|
options.hh \
|
||||||
|
output-options.hh \
|
||||||
|
shape-consumer.hh \
|
||||||
|
shape-options.hh \
|
||||||
|
text-options.hh \
|
||||||
view-cairo.hh \
|
view-cairo.hh \
|
||||||
|
view-options.hh \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
HB_SHAPE_sources = \
|
HB_SHAPE_sources = \
|
||||||
|
face-options.hh \
|
||||||
|
font-options.hh \
|
||||||
hb-shape.cc \
|
hb-shape.cc \
|
||||||
options.cc \
|
|
||||||
options.hh \
|
|
||||||
main-font-text.hh \
|
main-font-text.hh \
|
||||||
|
options.hh \
|
||||||
|
output-options.hh \
|
||||||
shape-consumer.hh \
|
shape-consumer.hh \
|
||||||
$(NULL)
|
shape-format.hh \
|
||||||
|
shape-options.hh \
|
||||||
HB_OT_SHAPE_CLOSURE_sources = \
|
text-options.hh \
|
||||||
hb-ot-shape-closure.cc \
|
|
||||||
options.cc \
|
|
||||||
options.hh \
|
|
||||||
main-font-text.hh \
|
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
HB_SUBSET_CLI_sources = \
|
HB_SUBSET_CLI_sources = \
|
||||||
|
face-options.hh \
|
||||||
hb-subset.cc \
|
hb-subset.cc \
|
||||||
options.cc \
|
|
||||||
options-subset.cc \
|
|
||||||
options.hh \
|
|
||||||
main-font-text.hh \
|
main-font-text.hh \
|
||||||
|
options.hh \
|
||||||
|
output-options.hh \
|
||||||
|
subset-options.hh \
|
||||||
|
text-options.hh \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
HB_OT_SHAPE_CLOSURE_sources = \
|
||||||
|
face-options.hh \
|
||||||
|
font-options.hh \
|
||||||
|
hb-ot-shape-closure.cc \
|
||||||
|
main-font-text.hh \
|
||||||
|
options.hh \
|
||||||
|
text-options.hh \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
|
@ -1,427 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2012 Google, Inc.
|
|
||||||
*
|
|
||||||
* This is part of HarfBuzz, a text shaping library.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, without written agreement and without
|
|
||||||
* license or royalty fees, to use, copy, modify, and distribute this
|
|
||||||
* software and its documentation for any purpose, provided that the
|
|
||||||
* above copyright notice and the following two paragraphs appear in
|
|
||||||
* all copies of this software.
|
|
||||||
*
|
|
||||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
|
||||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
|
||||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
* DAMAGE.
|
|
||||||
*
|
|
||||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
|
||||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
|
||||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
|
||||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
|
||||||
*
|
|
||||||
* Google Author(s): Behdad Esfahbod
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include "config.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "ansi-print.hh"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#ifdef HAVE_UNISTD_H
|
|
||||||
#include <unistd.h> /* for isatty() */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined (_MSC_VER) && (_MSC_VER < 1800)
|
|
||||||
static inline long int
|
|
||||||
lround (double x)
|
|
||||||
{
|
|
||||||
if (x >= 0)
|
|
||||||
return floor (x + 0.5);
|
|
||||||
else
|
|
||||||
return ceil (x - 0.5);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ESC_E (char)27
|
|
||||||
|
|
||||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
||||||
|
|
||||||
#define CELL_W 8
|
|
||||||
#define CELL_H (2 * CELL_W)
|
|
||||||
|
|
||||||
struct color_diff_t
|
|
||||||
{
|
|
||||||
int dot (const color_diff_t &o)
|
|
||||||
{ return v[0]*o.v[0] + v[1]*o.v[1] + v[2]*o.v[2] + v[3]*o.v[3]; }
|
|
||||||
|
|
||||||
int v[4];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct color_t
|
|
||||||
{
|
|
||||||
static color_t from_ansi (unsigned int x)
|
|
||||||
{
|
|
||||||
color_t c = {(0xFFu<<24) | ((0xFFu*(x&1))<<16) | ((0xFFu*((x >> 1)&1))<<8) | (0xFFu*((x >> 2)&1))};
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
unsigned int to_ansi ()
|
|
||||||
{
|
|
||||||
return ((v >> 23) & 1) | ((v >> 14)&2) | ((v >> 5)&4);
|
|
||||||
}
|
|
||||||
|
|
||||||
color_diff_t diff (const color_t &o)
|
|
||||||
{
|
|
||||||
color_diff_t d;
|
|
||||||
for (unsigned int i = 0; i < 4; i++)
|
|
||||||
d.v[i] = (int) ((v >> (i*8))&0xFF) - (int) ((o.v >> (i*8))&0xFF);
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t v;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct image_t
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
image_t (unsigned int width_,
|
|
||||||
unsigned int height_,
|
|
||||||
const uint32_t *data_,
|
|
||||||
unsigned int stride_) :
|
|
||||||
width (width_),
|
|
||||||
height (height_),
|
|
||||||
own_data (false),
|
|
||||||
data ((color_t *) data_),
|
|
||||||
stride (stride_) {}
|
|
||||||
image_t (unsigned int width_,
|
|
||||||
unsigned int height_) :
|
|
||||||
width (width_),
|
|
||||||
height (height_),
|
|
||||||
own_data (true),
|
|
||||||
data ((color_t *) malloc (sizeof (data[0]) * width * height)),
|
|
||||||
stride (width) {}
|
|
||||||
~image_t ()
|
|
||||||
{ if (own_data) free (data); }
|
|
||||||
|
|
||||||
color_t &operator () (unsigned int x, unsigned int y)
|
|
||||||
{ return data[x + y * stride]; }
|
|
||||||
|
|
||||||
color_t operator () (unsigned int x, unsigned int y) const
|
|
||||||
{ return data[x + y * stride]; }
|
|
||||||
|
|
||||||
void
|
|
||||||
copy_sub_image (const image_t &s,
|
|
||||||
unsigned int x, unsigned int y,
|
|
||||||
unsigned int w, unsigned int h)
|
|
||||||
{
|
|
||||||
assert (x < width);
|
|
||||||
assert (y < height);
|
|
||||||
for (unsigned int row = 0; row < h; row++) {
|
|
||||||
color_t *p = data + x + MIN (y + row, height - 1) * stride;
|
|
||||||
color_t *q = s.data + row * s.stride;
|
|
||||||
if (x + w <= width)
|
|
||||||
for (unsigned int col = 0; col < w; col++)
|
|
||||||
*q++ = *p++;
|
|
||||||
else {
|
|
||||||
unsigned int limit = width - x;
|
|
||||||
for (unsigned int col = 0; col < limit; col++)
|
|
||||||
*q++ = *p++;
|
|
||||||
p--;
|
|
||||||
for (unsigned int col = limit; col < w; col++)
|
|
||||||
*q++ = *p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned int width;
|
|
||||||
const unsigned int height;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool own_data;
|
|
||||||
color_t * const data;
|
|
||||||
const unsigned int stride;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct biimage_t
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
biimage_t (unsigned int width, unsigned int height) :
|
|
||||||
width (width),
|
|
||||||
height (height),
|
|
||||||
bg (0), fg (0), unicolor (true),
|
|
||||||
data ((uint8_t *) malloc (sizeof (data[0]) * width * height)) {}
|
|
||||||
~biimage_t ()
|
|
||||||
{ free (data); }
|
|
||||||
|
|
||||||
void set (const image_t &image)
|
|
||||||
{
|
|
||||||
assert (image.width == width);
|
|
||||||
assert (image.height == height);
|
|
||||||
int freq[8] = {0};
|
|
||||||
for (unsigned int y = 0; y < height; y++)
|
|
||||||
for (unsigned int x = 0; x < width; x++) {
|
|
||||||
color_t c = image (x, y);
|
|
||||||
freq[c.to_ansi ()]++;
|
|
||||||
}
|
|
||||||
bg = 0;
|
|
||||||
for (unsigned int i = 1; i < 8; i++)
|
|
||||||
if (freq[bg] < freq[i])
|
|
||||||
bg = i;
|
|
||||||
fg = 0;
|
|
||||||
for (unsigned int i = 1; i < 8; i++)
|
|
||||||
if (i != bg && freq[fg] < freq[i])
|
|
||||||
fg = i;
|
|
||||||
if (fg == bg || freq[fg] == 0) {
|
|
||||||
fg = bg;
|
|
||||||
unicolor = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
unicolor = false;
|
|
||||||
|
|
||||||
/* Set the data... */
|
|
||||||
|
|
||||||
if (unicolor) {
|
|
||||||
memset (data, 0, sizeof (data[0]) * width * height);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
color_t bgc = color_t::from_ansi (bg);
|
|
||||||
color_t fgc = color_t::from_ansi (fg);
|
|
||||||
color_diff_t diff = fgc.diff (bgc);
|
|
||||||
int dd = diff.dot (diff);
|
|
||||||
for (unsigned int y = 0; y < height; y++)
|
|
||||||
for (unsigned int x = 0; x < width; x++) {
|
|
||||||
int d = diff.dot (image (x, y).diff (bgc));
|
|
||||||
(*this)(x, y) = d < 0 ? 0 : d > dd ? 255 : lround (d * 255. / dd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t &operator () (unsigned int x, unsigned int y)
|
|
||||||
{ return data[x + y * width]; }
|
|
||||||
|
|
||||||
uint8_t operator () (unsigned int x, unsigned int y) const
|
|
||||||
{ return data[x + y * width]; }
|
|
||||||
|
|
||||||
const unsigned int width;
|
|
||||||
const unsigned int height;
|
|
||||||
unsigned int bg;
|
|
||||||
unsigned int fg;
|
|
||||||
bool unicolor;
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t * const data;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
block_best (const biimage_t &bi, bool *inverse)
|
|
||||||
{
|
|
||||||
assert (bi.width <= CELL_W);
|
|
||||||
assert (bi.height <= CELL_H);
|
|
||||||
|
|
||||||
unsigned int score = UINT_MAX;
|
|
||||||
unsigned int row_sum[CELL_H] = {0};
|
|
||||||
unsigned int col_sum[CELL_W] = {0};
|
|
||||||
unsigned int row_sum_i[CELL_H] = {0};
|
|
||||||
unsigned int col_sum_i[CELL_W] = {0};
|
|
||||||
unsigned int quad[2][2] = {{0}};
|
|
||||||
unsigned int quad_i[2][2] = {{0}};
|
|
||||||
unsigned int total = 0;
|
|
||||||
unsigned int total_i = 0;
|
|
||||||
for (unsigned int y = 0; y < bi.height; y++)
|
|
||||||
for (unsigned int x = 0; x < bi.width; x++) {
|
|
||||||
unsigned int c = bi (x, y);
|
|
||||||
unsigned int c_i = 255 - c;
|
|
||||||
row_sum[y] += c;
|
|
||||||
row_sum_i[y] += c_i;
|
|
||||||
col_sum[x] += c;
|
|
||||||
col_sum_i[x] += c_i;
|
|
||||||
quad[2 * y / bi.height][2 * x / bi.width] += c;
|
|
||||||
quad_i[2 * y / bi.height][2 * x / bi.width] += c_i;
|
|
||||||
total += c;
|
|
||||||
total_i += c_i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make the sums cummulative */
|
|
||||||
for (unsigned int i = 1; i < bi.height; i++) {
|
|
||||||
row_sum[i] += row_sum[i - 1];
|
|
||||||
row_sum_i[i] += row_sum_i[i - 1];
|
|
||||||
}
|
|
||||||
for (unsigned int i = 1; i < bi.width; i++) {
|
|
||||||
col_sum[i] += col_sum[i - 1];
|
|
||||||
col_sum_i[i] += col_sum_i[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *best_c = " ";
|
|
||||||
|
|
||||||
/* Maybe empty is better! */
|
|
||||||
if (total < score) {
|
|
||||||
score = total;
|
|
||||||
*inverse = false;
|
|
||||||
best_c = " ";
|
|
||||||
}
|
|
||||||
/* Maybe full is better! */
|
|
||||||
if (total_i < score) {
|
|
||||||
score = total_i;
|
|
||||||
*inverse = true;
|
|
||||||
best_c = " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find best lower line */
|
|
||||||
if (1) {
|
|
||||||
unsigned int best_s = UINT_MAX;
|
|
||||||
bool best_inv = false;
|
|
||||||
int best_i = 0;
|
|
||||||
for (unsigned int i = 0; i < bi.height - 1; i++)
|
|
||||||
{
|
|
||||||
unsigned int s;
|
|
||||||
s = row_sum[i] + total_i - row_sum_i[i];
|
|
||||||
if (s < best_s) {
|
|
||||||
best_s = s;
|
|
||||||
best_i = i;
|
|
||||||
best_inv = false;
|
|
||||||
}
|
|
||||||
s = row_sum_i[i] + total - row_sum[i];
|
|
||||||
if (s < best_s) {
|
|
||||||
best_s = s;
|
|
||||||
best_i = i;
|
|
||||||
best_inv = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (best_s < score) {
|
|
||||||
static const char *lower[7] = {"▁", "▂", "▃", "▄", "▅", "▆", "▇"};
|
|
||||||
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.height);
|
|
||||||
if (1 <= which && which <= 7) {
|
|
||||||
score = best_s;
|
|
||||||
*inverse = best_inv;
|
|
||||||
best_c = lower[7 - which];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find best left line */
|
|
||||||
if (1) {
|
|
||||||
unsigned int best_s = UINT_MAX;
|
|
||||||
bool best_inv = false;
|
|
||||||
int best_i = 0;
|
|
||||||
for (unsigned int i = 0; i < bi.width - 1; i++)
|
|
||||||
{
|
|
||||||
unsigned int s;
|
|
||||||
s = col_sum[i] + total_i - col_sum_i[i];
|
|
||||||
if (s < best_s) {
|
|
||||||
best_s = s;
|
|
||||||
best_i = i;
|
|
||||||
best_inv = true;
|
|
||||||
}
|
|
||||||
s = col_sum_i[i] + total - col_sum[i];
|
|
||||||
if (s < best_s) {
|
|
||||||
best_s = s;
|
|
||||||
best_i = i;
|
|
||||||
best_inv = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (best_s < score) {
|
|
||||||
static const char *left [7] = {"▏", "▎", "▍", "▌", "▋", "▊", "▉"};
|
|
||||||
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.width);
|
|
||||||
if (1 <= which && which <= 7) {
|
|
||||||
score = best_s;
|
|
||||||
*inverse = best_inv;
|
|
||||||
best_c = left[which - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find best quadrant */
|
|
||||||
if (1) {
|
|
||||||
unsigned int q = 0;
|
|
||||||
unsigned int qs = 0;
|
|
||||||
for (unsigned int i = 0; i < 2; i++)
|
|
||||||
for (unsigned int j = 0; j < 2; j++)
|
|
||||||
if (quad[i][j] > quad_i[i][j]) {
|
|
||||||
q += 1 << (2 * i + j);
|
|
||||||
qs += quad_i[i][j];
|
|
||||||
} else
|
|
||||||
qs += quad[i][j];
|
|
||||||
if (qs < score) {
|
|
||||||
const char *c = nullptr;
|
|
||||||
bool inv = false;
|
|
||||||
switch (q) {
|
|
||||||
case 1: c = "▟"; inv = true; break;
|
|
||||||
case 2: c = "▙"; inv = true; break;
|
|
||||||
case 4: c = "▖"; inv = false; break;
|
|
||||||
case 8: c = "▗"; inv = false; break;
|
|
||||||
case 9: c = "▚"; inv = false; break;
|
|
||||||
case 6: c = "▞"; inv = false; break;
|
|
||||||
case 7: c = "▜"; inv = true; break;
|
|
||||||
case 11: c = "▜"; inv = true; break;
|
|
||||||
case 13: c = "▙"; inv = true; break;
|
|
||||||
case 14: c = "▟"; inv = true; break;
|
|
||||||
}
|
|
||||||
if (c) {
|
|
||||||
score = qs;
|
|
||||||
*inverse = inv;
|
|
||||||
best_c = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return best_c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ansi_print_image_rgb24 (const uint32_t *data,
|
|
||||||
unsigned int width,
|
|
||||||
unsigned int height,
|
|
||||||
unsigned int stride)
|
|
||||||
{
|
|
||||||
image_t image (width, height, data, stride);
|
|
||||||
|
|
||||||
unsigned int rows = (height + CELL_H - 1) / CELL_H;
|
|
||||||
unsigned int cols = (width + CELL_W - 1) / CELL_W;
|
|
||||||
image_t cell (CELL_W, CELL_H);
|
|
||||||
biimage_t bi (CELL_W, CELL_H);
|
|
||||||
unsigned int last_bg = -1, last_fg = -1;
|
|
||||||
for (unsigned int row = 0; row < rows; row++) {
|
|
||||||
for (unsigned int col = 0; col < cols; col++) {
|
|
||||||
image.copy_sub_image (cell, col * CELL_W, row * CELL_H, CELL_W, CELL_H);
|
|
||||||
bi.set (cell);
|
|
||||||
if (bi.unicolor) {
|
|
||||||
if (last_bg != bi.bg) {
|
|
||||||
printf ("%c[%dm", ESC_E, 40 + bi.bg);
|
|
||||||
last_bg = bi.bg;
|
|
||||||
}
|
|
||||||
printf (" ");
|
|
||||||
} else {
|
|
||||||
/* Figure out the closest character to the biimage */
|
|
||||||
bool inverse = false;
|
|
||||||
const char *c = block_best (bi, &inverse);
|
|
||||||
if (inverse) {
|
|
||||||
if (last_bg != bi.fg || last_fg != bi.bg) {
|
|
||||||
printf ("%c[%d;%dm", ESC_E, 30 + bi.bg, 40 + bi.fg);
|
|
||||||
last_bg = bi.fg;
|
|
||||||
last_fg = bi.bg;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (last_bg != bi.bg || last_fg != bi.fg) {
|
|
||||||
printf ("%c[%d;%dm", ESC_E, 40 + bi.bg, 30 + bi.fg);
|
|
||||||
last_bg = bi.bg;
|
|
||||||
last_fg = bi.fg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf ("%s", c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf ("%c[0m\n", ESC_E); /* Reset */
|
|
||||||
last_bg = last_fg = -1;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,11 +29,398 @@
|
||||||
|
|
||||||
#include "hb.hh"
|
#include "hb.hh"
|
||||||
|
|
||||||
void
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
#include <unistd.h> /* for isatty() */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (_MSC_VER) && (_MSC_VER < 1800)
|
||||||
|
static inline long int
|
||||||
|
lround (double x)
|
||||||
|
{
|
||||||
|
if (x >= 0)
|
||||||
|
return floor (x + 0.5);
|
||||||
|
else
|
||||||
|
return ceil (x - 0.5);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ESC_E (char)27
|
||||||
|
|
||||||
|
#define CELL_W 8
|
||||||
|
#define CELL_H (2 * CELL_W)
|
||||||
|
|
||||||
|
struct color_diff_t
|
||||||
|
{
|
||||||
|
int dot (const color_diff_t &o)
|
||||||
|
{ return v[0]*o.v[0] + v[1]*o.v[1] + v[2]*o.v[2] + v[3]*o.v[3]; }
|
||||||
|
|
||||||
|
int v[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct color_t
|
||||||
|
{
|
||||||
|
static color_t from_ansi (unsigned int x)
|
||||||
|
{
|
||||||
|
color_t c = {(0xFFu<<24) | ((0xFFu*(x&1))<<16) | ((0xFFu*((x >> 1)&1))<<8) | (0xFFu*((x >> 2)&1))};
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
unsigned int to_ansi ()
|
||||||
|
{
|
||||||
|
return ((v >> 23) & 1) | ((v >> 14)&2) | ((v >> 5)&4);
|
||||||
|
}
|
||||||
|
|
||||||
|
color_diff_t diff (const color_t &o)
|
||||||
|
{
|
||||||
|
color_diff_t d;
|
||||||
|
for (unsigned int i = 0; i < 4; i++)
|
||||||
|
d.v[i] = (int) ((v >> (i*8))&0xFF) - (int) ((o.v >> (i*8))&0xFF);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t v;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct image_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
image_t (unsigned int width_,
|
||||||
|
unsigned int height_,
|
||||||
|
const uint32_t *data_,
|
||||||
|
unsigned int stride_) :
|
||||||
|
width (width_),
|
||||||
|
height (height_),
|
||||||
|
own_data (false),
|
||||||
|
data ((color_t *) data_),
|
||||||
|
stride (stride_) {}
|
||||||
|
image_t (unsigned int width_,
|
||||||
|
unsigned int height_) :
|
||||||
|
width (width_),
|
||||||
|
height (height_),
|
||||||
|
own_data (true),
|
||||||
|
data ((color_t *) malloc (sizeof (data[0]) * width * height)),
|
||||||
|
stride (width) {}
|
||||||
|
~image_t ()
|
||||||
|
{ if (own_data) free (data); }
|
||||||
|
|
||||||
|
color_t &operator () (unsigned int x, unsigned int y)
|
||||||
|
{ return data[x + y * stride]; }
|
||||||
|
|
||||||
|
color_t operator () (unsigned int x, unsigned int y) const
|
||||||
|
{ return data[x + y * stride]; }
|
||||||
|
|
||||||
|
void
|
||||||
|
copy_sub_image (const image_t &s,
|
||||||
|
unsigned int x, unsigned int y,
|
||||||
|
unsigned int w, unsigned int h)
|
||||||
|
{
|
||||||
|
assert (x < width);
|
||||||
|
assert (y < height);
|
||||||
|
for (unsigned int row = 0; row < h; row++) {
|
||||||
|
color_t *p = data + x + hb_min (y + row, height - 1) * stride;
|
||||||
|
color_t *q = s.data + row * s.stride;
|
||||||
|
if (x + w <= width)
|
||||||
|
for (unsigned int col = 0; col < w; col++)
|
||||||
|
*q++ = *p++;
|
||||||
|
else {
|
||||||
|
unsigned int limit = width - x;
|
||||||
|
for (unsigned int col = 0; col < limit; col++)
|
||||||
|
*q++ = *p++;
|
||||||
|
p--;
|
||||||
|
for (unsigned int col = limit; col < w; col++)
|
||||||
|
*q++ = *p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned int width;
|
||||||
|
const unsigned int height;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool own_data;
|
||||||
|
color_t * const data;
|
||||||
|
const unsigned int stride;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct biimage_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
biimage_t (unsigned int width, unsigned int height) :
|
||||||
|
width (width),
|
||||||
|
height (height),
|
||||||
|
bg (0), fg (0), unicolor (true),
|
||||||
|
data ((uint8_t *) malloc (sizeof (data[0]) * width * height)) {}
|
||||||
|
~biimage_t ()
|
||||||
|
{ free (data); }
|
||||||
|
|
||||||
|
void set (const image_t &image)
|
||||||
|
{
|
||||||
|
assert (image.width == width);
|
||||||
|
assert (image.height == height);
|
||||||
|
int freq[8] = {0};
|
||||||
|
for (unsigned int y = 0; y < height; y++)
|
||||||
|
for (unsigned int x = 0; x < width; x++) {
|
||||||
|
color_t c = image (x, y);
|
||||||
|
freq[c.to_ansi ()]++;
|
||||||
|
}
|
||||||
|
bg = 0;
|
||||||
|
for (unsigned int i = 1; i < 8; i++)
|
||||||
|
if (freq[bg] < freq[i])
|
||||||
|
bg = i;
|
||||||
|
fg = 0;
|
||||||
|
for (unsigned int i = 1; i < 8; i++)
|
||||||
|
if (i != bg && freq[fg] < freq[i])
|
||||||
|
fg = i;
|
||||||
|
if (fg == bg || freq[fg] == 0) {
|
||||||
|
fg = bg;
|
||||||
|
unicolor = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
unicolor = false;
|
||||||
|
|
||||||
|
/* Set the data... */
|
||||||
|
|
||||||
|
if (unicolor) {
|
||||||
|
memset (data, 0, sizeof (data[0]) * width * height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_t bgc = color_t::from_ansi (bg);
|
||||||
|
color_t fgc = color_t::from_ansi (fg);
|
||||||
|
color_diff_t diff = fgc.diff (bgc);
|
||||||
|
int dd = diff.dot (diff);
|
||||||
|
for (unsigned int y = 0; y < height; y++)
|
||||||
|
for (unsigned int x = 0; x < width; x++) {
|
||||||
|
int d = diff.dot (image (x, y).diff (bgc));
|
||||||
|
(*this)(x, y) = d < 0 ? 0 : d > dd ? 255 : lround (d * 255. / dd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t &operator () (unsigned int x, unsigned int y)
|
||||||
|
{ return data[x + y * width]; }
|
||||||
|
|
||||||
|
uint8_t operator () (unsigned int x, unsigned int y) const
|
||||||
|
{ return data[x + y * width]; }
|
||||||
|
|
||||||
|
const unsigned int width;
|
||||||
|
const unsigned int height;
|
||||||
|
unsigned int bg;
|
||||||
|
unsigned int fg;
|
||||||
|
bool unicolor;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t * const data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
block_best (const biimage_t &bi, bool *inverse)
|
||||||
|
{
|
||||||
|
assert (bi.width <= CELL_W);
|
||||||
|
assert (bi.height <= CELL_H);
|
||||||
|
|
||||||
|
unsigned int score = UINT_MAX;
|
||||||
|
unsigned int row_sum[CELL_H] = {0};
|
||||||
|
unsigned int col_sum[CELL_W] = {0};
|
||||||
|
unsigned int row_sum_i[CELL_H] = {0};
|
||||||
|
unsigned int col_sum_i[CELL_W] = {0};
|
||||||
|
unsigned int quad[2][2] = {{0}};
|
||||||
|
unsigned int quad_i[2][2] = {{0}};
|
||||||
|
unsigned int total = 0;
|
||||||
|
unsigned int total_i = 0;
|
||||||
|
for (unsigned int y = 0; y < bi.height; y++)
|
||||||
|
for (unsigned int x = 0; x < bi.width; x++) {
|
||||||
|
unsigned int c = bi (x, y);
|
||||||
|
unsigned int c_i = 255 - c;
|
||||||
|
row_sum[y] += c;
|
||||||
|
row_sum_i[y] += c_i;
|
||||||
|
col_sum[x] += c;
|
||||||
|
col_sum_i[x] += c_i;
|
||||||
|
quad[2 * y / bi.height][2 * x / bi.width] += c;
|
||||||
|
quad_i[2 * y / bi.height][2 * x / bi.width] += c_i;
|
||||||
|
total += c;
|
||||||
|
total_i += c_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the sums cummulative */
|
||||||
|
for (unsigned int i = 1; i < bi.height; i++) {
|
||||||
|
row_sum[i] += row_sum[i - 1];
|
||||||
|
row_sum_i[i] += row_sum_i[i - 1];
|
||||||
|
}
|
||||||
|
for (unsigned int i = 1; i < bi.width; i++) {
|
||||||
|
col_sum[i] += col_sum[i - 1];
|
||||||
|
col_sum_i[i] += col_sum_i[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *best_c = " ";
|
||||||
|
|
||||||
|
/* Maybe empty is better! */
|
||||||
|
if (total < score) {
|
||||||
|
score = total;
|
||||||
|
*inverse = false;
|
||||||
|
best_c = " ";
|
||||||
|
}
|
||||||
|
/* Maybe full is better! */
|
||||||
|
if (total_i < score) {
|
||||||
|
score = total_i;
|
||||||
|
*inverse = true;
|
||||||
|
best_c = " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find best lower line */
|
||||||
|
if (1) {
|
||||||
|
unsigned int best_s = UINT_MAX;
|
||||||
|
bool best_inv = false;
|
||||||
|
int best_i = 0;
|
||||||
|
for (unsigned int i = 0; i < bi.height - 1; i++)
|
||||||
|
{
|
||||||
|
unsigned int s;
|
||||||
|
s = row_sum[i] + total_i - row_sum_i[i];
|
||||||
|
if (s < best_s) {
|
||||||
|
best_s = s;
|
||||||
|
best_i = i;
|
||||||
|
best_inv = false;
|
||||||
|
}
|
||||||
|
s = row_sum_i[i] + total - row_sum[i];
|
||||||
|
if (s < best_s) {
|
||||||
|
best_s = s;
|
||||||
|
best_i = i;
|
||||||
|
best_inv = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_s < score) {
|
||||||
|
static const char *lower[7] = {"▁", "▂", "▃", "▄", "▅", "▆", "▇"};
|
||||||
|
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.height);
|
||||||
|
if (1 <= which && which <= 7) {
|
||||||
|
score = best_s;
|
||||||
|
*inverse = best_inv;
|
||||||
|
best_c = lower[7 - which];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find best left line */
|
||||||
|
if (1) {
|
||||||
|
unsigned int best_s = UINT_MAX;
|
||||||
|
bool best_inv = false;
|
||||||
|
int best_i = 0;
|
||||||
|
for (unsigned int i = 0; i < bi.width - 1; i++)
|
||||||
|
{
|
||||||
|
unsigned int s;
|
||||||
|
s = col_sum[i] + total_i - col_sum_i[i];
|
||||||
|
if (s < best_s) {
|
||||||
|
best_s = s;
|
||||||
|
best_i = i;
|
||||||
|
best_inv = true;
|
||||||
|
}
|
||||||
|
s = col_sum_i[i] + total - col_sum[i];
|
||||||
|
if (s < best_s) {
|
||||||
|
best_s = s;
|
||||||
|
best_i = i;
|
||||||
|
best_inv = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_s < score) {
|
||||||
|
static const char *left [7] = {"▏", "▎", "▍", "▌", "▋", "▊", "▉"};
|
||||||
|
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.width);
|
||||||
|
if (1 <= which && which <= 7) {
|
||||||
|
score = best_s;
|
||||||
|
*inverse = best_inv;
|
||||||
|
best_c = left[which - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find best quadrant */
|
||||||
|
if (1) {
|
||||||
|
unsigned int q = 0;
|
||||||
|
unsigned int qs = 0;
|
||||||
|
for (unsigned int i = 0; i < 2; i++)
|
||||||
|
for (unsigned int j = 0; j < 2; j++)
|
||||||
|
if (quad[i][j] > quad_i[i][j]) {
|
||||||
|
q += 1 << (2 * i + j);
|
||||||
|
qs += quad_i[i][j];
|
||||||
|
} else
|
||||||
|
qs += quad[i][j];
|
||||||
|
if (qs < score) {
|
||||||
|
const char *c = nullptr;
|
||||||
|
bool inv = false;
|
||||||
|
switch (q) {
|
||||||
|
case 1: c = "▟"; inv = true; break;
|
||||||
|
case 2: c = "▙"; inv = true; break;
|
||||||
|
case 4: c = "▖"; inv = false; break;
|
||||||
|
case 8: c = "▗"; inv = false; break;
|
||||||
|
case 9: c = "▚"; inv = false; break;
|
||||||
|
case 6: c = "▞"; inv = false; break;
|
||||||
|
case 7: c = "▜"; inv = true; break;
|
||||||
|
case 11: c = "▜"; inv = true; break;
|
||||||
|
case 13: c = "▙"; inv = true; break;
|
||||||
|
case 14: c = "▟"; inv = true; break;
|
||||||
|
}
|
||||||
|
if (c) {
|
||||||
|
score = qs;
|
||||||
|
*inverse = inv;
|
||||||
|
best_c = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
ansi_print_image_rgb24 (const uint32_t *data,
|
ansi_print_image_rgb24 (const uint32_t *data,
|
||||||
unsigned int width,
|
unsigned int width,
|
||||||
unsigned int height,
|
unsigned int height,
|
||||||
unsigned int stride);
|
unsigned int stride)
|
||||||
|
{
|
||||||
|
image_t image (width, height, data, stride);
|
||||||
|
|
||||||
|
unsigned int rows = (height + CELL_H - 1) / CELL_H;
|
||||||
|
unsigned int cols = (width + CELL_W - 1) / CELL_W;
|
||||||
|
image_t cell (CELL_W, CELL_H);
|
||||||
|
biimage_t bi (CELL_W, CELL_H);
|
||||||
|
unsigned int last_bg = -1, last_fg = -1;
|
||||||
|
for (unsigned int row = 0; row < rows; row++) {
|
||||||
|
for (unsigned int col = 0; col < cols; col++) {
|
||||||
|
image.copy_sub_image (cell, col * CELL_W, row * CELL_H, CELL_W, CELL_H);
|
||||||
|
bi.set (cell);
|
||||||
|
if (bi.unicolor) {
|
||||||
|
if (last_bg != bi.bg) {
|
||||||
|
printf ("%c[%dm", ESC_E, 40 + bi.bg);
|
||||||
|
last_bg = bi.bg;
|
||||||
|
}
|
||||||
|
printf (" ");
|
||||||
|
} else {
|
||||||
|
/* Figure out the closest character to the biimage */
|
||||||
|
bool inverse = false;
|
||||||
|
const char *c = block_best (bi, &inverse);
|
||||||
|
if (inverse) {
|
||||||
|
if (last_bg != bi.fg || last_fg != bi.bg) {
|
||||||
|
printf ("%c[%d;%dm", ESC_E, 30 + bi.bg, 40 + bi.fg);
|
||||||
|
last_bg = bi.fg;
|
||||||
|
last_fg = bi.bg;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (last_bg != bi.bg || last_fg != bi.fg) {
|
||||||
|
printf ("%c[%d;%dm", ESC_E, 40 + bi.bg, 30 + bi.fg);
|
||||||
|
last_bg = bi.bg;
|
||||||
|
last_fg = bi.fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf ("%s", c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf ("%c[0m\n", ESC_E); /* Reset */
|
||||||
|
last_bg = last_fg = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Google, Inc.
|
||||||
|
*
|
||||||
|
* This is part of HarfBuzz, a text shaping library.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, without written agreement and without
|
||||||
|
* license or royalty fees, to use, copy, modify, and distribute this
|
||||||
|
* software and its documentation for any purpose, provided that the
|
||||||
|
* above copyright notice and the following two paragraphs appear in
|
||||||
|
* all copies of this software.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||||
|
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||||
|
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||||
|
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||||
|
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||||
|
*
|
||||||
|
* Google Author(s): Behdad Esfahbod
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FACE_OPTIONS_HH
|
||||||
|
#define FACE_OPTIONS_HH
|
||||||
|
|
||||||
|
#include "options.hh"
|
||||||
|
|
||||||
|
struct face_options_t
|
||||||
|
{
|
||||||
|
void add_options (option_parser_t *parser);
|
||||||
|
|
||||||
|
hb_blob_t *get_blob () const;
|
||||||
|
hb_face_t *get_face () const;
|
||||||
|
|
||||||
|
static struct cache_t
|
||||||
|
{
|
||||||
|
~cache_t ()
|
||||||
|
{
|
||||||
|
free ((void *) font_path);
|
||||||
|
hb_blob_destroy (blob);
|
||||||
|
hb_face_destroy (face);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *font_path = nullptr;
|
||||||
|
hb_blob_t *blob = nullptr;
|
||||||
|
unsigned face_index = (unsigned) -1;
|
||||||
|
hb_face_t *face = nullptr;
|
||||||
|
} cache;
|
||||||
|
|
||||||
|
char *font_file = nullptr;
|
||||||
|
unsigned face_index = 0;
|
||||||
|
private:
|
||||||
|
mutable hb_face_t *face = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
face_options_t::cache_t face_options_t::cache {};
|
||||||
|
|
||||||
|
hb_blob_t *
|
||||||
|
face_options_t::get_blob () const
|
||||||
|
{
|
||||||
|
// XXX This does the job for now; will move to post_parse.
|
||||||
|
return cache.blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
hb_face_t *
|
||||||
|
face_options_t::get_face () const
|
||||||
|
{
|
||||||
|
if (face)
|
||||||
|
return face;
|
||||||
|
|
||||||
|
if (!font_file)
|
||||||
|
fail (true, "No font file set");
|
||||||
|
|
||||||
|
const char *font_path = font_file;
|
||||||
|
|
||||||
|
if (0 == strcmp (font_path, "-"))
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||||
|
setmode (fileno (stdin), O_BINARY);
|
||||||
|
font_path = "STDIN";
|
||||||
|
#else
|
||||||
|
font_path = "/dev/stdin";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cache.font_path || 0 != strcmp (cache.font_path, font_path))
|
||||||
|
{
|
||||||
|
hb_blob_destroy (cache.blob);
|
||||||
|
cache.blob = hb_blob_create_from_file_or_fail (font_path);
|
||||||
|
|
||||||
|
free ((char *) cache.font_path);
|
||||||
|
cache.font_path = strdup (font_path);
|
||||||
|
|
||||||
|
if (!cache.blob)
|
||||||
|
fail (false, "%s: Failed reading file", font_path);
|
||||||
|
|
||||||
|
hb_face_destroy (cache.face);
|
||||||
|
cache.face = nullptr;
|
||||||
|
cache.face_index = (unsigned) -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.face_index != face_index)
|
||||||
|
{
|
||||||
|
hb_face_destroy (cache.face);
|
||||||
|
cache.face = hb_face_create (cache.blob, face_index);
|
||||||
|
cache.face_index = face_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
face = cache.face;
|
||||||
|
|
||||||
|
return face;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
face_options_t::add_options (option_parser_t *parser)
|
||||||
|
{
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{"font-file", 0, 0, G_OPTION_ARG_STRING, &this->font_file, "Set font file-name", "filename"},
|
||||||
|
{"face-index", 0, 0, G_OPTION_ARG_INT, &this->face_index, "Set face index (default: 0)", "index"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries,
|
||||||
|
"face",
|
||||||
|
"Font-face options:",
|
||||||
|
"Options for the font face",
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Google, Inc.
|
||||||
|
*
|
||||||
|
* This is part of HarfBuzz, a text shaping library.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, without written agreement and without
|
||||||
|
* license or royalty fees, to use, copy, modify, and distribute this
|
||||||
|
* software and its documentation for any purpose, provided that the
|
||||||
|
* above copyright notice and the following two paragraphs appear in
|
||||||
|
* all copies of this software.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||||
|
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||||
|
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||||
|
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||||
|
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||||
|
*
|
||||||
|
* Google Author(s): Behdad Esfahbod
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FONT_OPTIONS_HH
|
||||||
|
#define FONT_OPTIONS_HH
|
||||||
|
|
||||||
|
#include "face-options.hh"
|
||||||
|
|
||||||
|
#ifdef HAVE_FREETYPE
|
||||||
|
#include <hb-ft.h>
|
||||||
|
#endif
|
||||||
|
#include <hb-ot.h>
|
||||||
|
|
||||||
|
#define FONT_SIZE_UPEM 0x7FFFFFFF
|
||||||
|
#define FONT_SIZE_NONE 0
|
||||||
|
|
||||||
|
extern const unsigned DEFAULT_FONT_SIZE;
|
||||||
|
extern const unsigned SUBPIXEL_BITS;
|
||||||
|
|
||||||
|
struct font_options_t : face_options_t
|
||||||
|
{
|
||||||
|
~font_options_t ()
|
||||||
|
{
|
||||||
|
g_free (font_file);
|
||||||
|
free (variations);
|
||||||
|
g_free (font_funcs);
|
||||||
|
hb_font_destroy (font);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_options (option_parser_t *parser);
|
||||||
|
|
||||||
|
hb_font_t *get_font () const;
|
||||||
|
|
||||||
|
hb_variation_t *variations = nullptr;
|
||||||
|
unsigned int num_variations = 0;
|
||||||
|
int x_ppem = 0;
|
||||||
|
int y_ppem = 0;
|
||||||
|
double ptem = 0.;
|
||||||
|
unsigned int subpixel_bits = SUBPIXEL_BITS;
|
||||||
|
mutable double font_size_x = DEFAULT_FONT_SIZE;
|
||||||
|
mutable double font_size_y = DEFAULT_FONT_SIZE;
|
||||||
|
char *font_funcs = nullptr;
|
||||||
|
int ft_load_flags = 2;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable hb_font_t *font = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct supported_font_funcs_t {
|
||||||
|
char name[4];
|
||||||
|
void (*func) (hb_font_t *);
|
||||||
|
} supported_font_funcs[] =
|
||||||
|
{
|
||||||
|
#ifdef HAVE_FREETYPE
|
||||||
|
{"ft", hb_ft_font_set_funcs},
|
||||||
|
#endif
|
||||||
|
{"ot", hb_ot_font_set_funcs},
|
||||||
|
};
|
||||||
|
|
||||||
|
hb_font_t *
|
||||||
|
font_options_t::get_font () const
|
||||||
|
{
|
||||||
|
if (font)
|
||||||
|
return font;
|
||||||
|
|
||||||
|
auto *face = get_face ();
|
||||||
|
|
||||||
|
font = hb_font_create (face);
|
||||||
|
|
||||||
|
if (font_size_x == FONT_SIZE_UPEM)
|
||||||
|
font_size_x = hb_face_get_upem (face);
|
||||||
|
if (font_size_y == FONT_SIZE_UPEM)
|
||||||
|
font_size_y = hb_face_get_upem (face);
|
||||||
|
|
||||||
|
hb_font_set_ppem (font, x_ppem, y_ppem);
|
||||||
|
hb_font_set_ptem (font, ptem);
|
||||||
|
|
||||||
|
int scale_x = (int) scalbnf (font_size_x, subpixel_bits);
|
||||||
|
int scale_y = (int) scalbnf (font_size_y, subpixel_bits);
|
||||||
|
hb_font_set_scale (font, scale_x, scale_y);
|
||||||
|
|
||||||
|
hb_font_set_variations (font, variations, num_variations);
|
||||||
|
|
||||||
|
void (*set_font_funcs) (hb_font_t *) = nullptr;
|
||||||
|
if (!font_funcs)
|
||||||
|
{
|
||||||
|
set_font_funcs = supported_font_funcs[0].func;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
|
||||||
|
if (0 == g_ascii_strcasecmp (font_funcs, supported_font_funcs[i].name))
|
||||||
|
{
|
||||||
|
set_font_funcs = supported_font_funcs[i].func;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!set_font_funcs)
|
||||||
|
{
|
||||||
|
GString *s = g_string_new (nullptr);
|
||||||
|
for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
|
||||||
|
{
|
||||||
|
if (i)
|
||||||
|
g_string_append_c (s, '/');
|
||||||
|
g_string_append (s, supported_font_funcs[i].name);
|
||||||
|
}
|
||||||
|
char *p = g_string_free (s, FALSE);
|
||||||
|
fail (false, "Unknown font function implementation `%s'; supported values are: %s; default is %s",
|
||||||
|
font_funcs,
|
||||||
|
p,
|
||||||
|
supported_font_funcs[0].name);
|
||||||
|
//free (p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_font_funcs (font);
|
||||||
|
#ifdef HAVE_FREETYPE
|
||||||
|
hb_ft_font_set_load_flags (font, ft_load_flags);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_variations (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
font_options_t *font_opts = (font_options_t *) data;
|
||||||
|
char *s = (char *) arg;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
font_opts->num_variations = 0;
|
||||||
|
g_free (font_opts->variations);
|
||||||
|
font_opts->variations = nullptr;
|
||||||
|
|
||||||
|
if (!*s)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* count the variations first, so we can allocate memory */
|
||||||
|
p = s;
|
||||||
|
do {
|
||||||
|
font_opts->num_variations++;
|
||||||
|
p = strchr (p, ',');
|
||||||
|
if (p)
|
||||||
|
p++;
|
||||||
|
} while (p);
|
||||||
|
|
||||||
|
font_opts->variations = (hb_variation_t *) calloc (font_opts->num_variations, sizeof (*font_opts->variations));
|
||||||
|
if (!font_opts->variations)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* now do the actual parsing */
|
||||||
|
p = s;
|
||||||
|
font_opts->num_variations = 0;
|
||||||
|
while (p && *p) {
|
||||||
|
char *end = strchr (p, ',');
|
||||||
|
if (hb_variation_from_string (p, end ? end - p : -1, &font_opts->variations[font_opts->num_variations]))
|
||||||
|
font_opts->num_variations++;
|
||||||
|
p = end ? end + 1 : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_font_size (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
font_options_t *font_opts = (font_options_t *) data;
|
||||||
|
if (0 == strcmp (arg, "upem"))
|
||||||
|
{
|
||||||
|
font_opts->font_size_y = font_opts->font_size_x = FONT_SIZE_UPEM;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (sscanf (arg, "%lf%*[ ,]%lf", &font_opts->font_size_x, &font_opts->font_size_y)) {
|
||||||
|
case 1: font_opts->font_size_y = font_opts->font_size_x; HB_FALLTHROUGH;
|
||||||
|
case 2: return true;
|
||||||
|
default:
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"%s argument should be one or two space-separated numbers",
|
||||||
|
name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_font_ppem (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
font_options_t *font_opts = (font_options_t *) data;
|
||||||
|
switch (sscanf (arg, "%d%*[ ,]%d", &font_opts->x_ppem, &font_opts->y_ppem)) {
|
||||||
|
case 1: font_opts->y_ppem = font_opts->x_ppem; HB_FALLTHROUGH;
|
||||||
|
case 2: return true;
|
||||||
|
default:
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"%s argument should be one or two space-separated numbers",
|
||||||
|
name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
font_options_t::add_options (option_parser_t *parser)
|
||||||
|
{
|
||||||
|
face_options_t::add_options (parser);
|
||||||
|
|
||||||
|
char *text = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
|
static_assert ((ARRAY_LENGTH_CONST (supported_font_funcs) > 0),
|
||||||
|
"No supported font-funcs found.");
|
||||||
|
GString *s = g_string_new (nullptr);
|
||||||
|
g_string_printf (s, "Set font functions implementation to use (default: %s)\n\n Supported font function implementations are: %s",
|
||||||
|
supported_font_funcs[0].name,
|
||||||
|
supported_font_funcs[0].name);
|
||||||
|
for (unsigned int i = 1; i < ARRAY_LENGTH (supported_font_funcs); i++)
|
||||||
|
{
|
||||||
|
g_string_append_c (s, '/');
|
||||||
|
g_string_append (s, supported_font_funcs[i].name);
|
||||||
|
}
|
||||||
|
text = g_string_free (s, FALSE);
|
||||||
|
parser->free_later (text);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *font_size_text;
|
||||||
|
if (DEFAULT_FONT_SIZE == FONT_SIZE_UPEM)
|
||||||
|
font_size_text = (char *) "Font size (default: upem)";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
font_size_text = g_strdup_printf ("Font size (default: %d)", DEFAULT_FONT_SIZE);
|
||||||
|
parser->free_later (font_size_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
int font_size_flags = DEFAULT_FONT_SIZE == FONT_SIZE_NONE ? G_OPTION_FLAG_HIDDEN : 0;
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{"font-size", 0, font_size_flags,
|
||||||
|
G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_size, font_size_text, "1/2 integers or 'upem'"},
|
||||||
|
{"font-ppem", 0, font_size_flags,
|
||||||
|
G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_ppem, "Set x,y pixels per EM (default: 0; disabled)", "1/2 integers"},
|
||||||
|
{"font-ptem", 0, 0,
|
||||||
|
G_OPTION_ARG_DOUBLE, &this->ptem, "Set font point-size (default: 0; disabled)", "point-size"},
|
||||||
|
{"font-funcs", 0, 0, G_OPTION_ARG_STRING, &this->font_funcs, text, "impl"},
|
||||||
|
{"ft-load-flags", 0, 0, G_OPTION_ARG_INT, &this->ft_load_flags, "Set FreeType load-flags (default: 2)", "integer"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries,
|
||||||
|
"font",
|
||||||
|
"Font-instance options:",
|
||||||
|
"Options for the font instance",
|
||||||
|
this);
|
||||||
|
|
||||||
|
const gchar *variations_help = "Comma-separated list of font variations\n"
|
||||||
|
"\n"
|
||||||
|
" Variations are set globally. The format for specifying variation settings\n"
|
||||||
|
" follows. All valid CSS font-variation-settings values other than 'normal'\n"
|
||||||
|
" and 'inherited' are also accepted, though, not documented below.\n"
|
||||||
|
"\n"
|
||||||
|
" The format is a tag, optionally followed by an equals sign, followed by a\n"
|
||||||
|
" number. For example:\n"
|
||||||
|
"\n"
|
||||||
|
" \"wght=500\"\n"
|
||||||
|
" \"slnt=-7.5\"\n";
|
||||||
|
|
||||||
|
GOptionEntry entries2[] =
|
||||||
|
{
|
||||||
|
{"variations", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_variations, variations_help, "list"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries2,
|
||||||
|
"variations",
|
||||||
|
"Variations options:",
|
||||||
|
"Options for font variations used",
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -24,23 +24,24 @@
|
||||||
* Google Author(s): Behdad Esfahbod
|
* Google Author(s): Behdad Esfahbod
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "shape-options.hh"
|
||||||
|
#include "font-options.hh"
|
||||||
|
#include "text-options.hh"
|
||||||
#include "main-font-text.hh"
|
#include "main-font-text.hh"
|
||||||
|
|
||||||
|
const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_NONE;
|
||||||
|
const unsigned SUBPIXEL_BITS = 0;
|
||||||
|
|
||||||
#ifdef HAVE_FREETYPE
|
#ifdef HAVE_FREETYPE
|
||||||
#include <hb-ft.h>
|
#include <hb-ft.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct shape_closure_consumer_t : option_group_t
|
struct shape_closure_consumer_t
|
||||||
{
|
{
|
||||||
shape_closure_consumer_t (option_parser_t *parser) :
|
void add_options (struct option_parser_t *parser)
|
||||||
shaper (parser),
|
|
||||||
show_glyph_names (true)
|
|
||||||
{
|
{
|
||||||
add_options (parser);
|
shaper.add_options (parser);
|
||||||
}
|
|
||||||
|
|
||||||
void add_options (struct option_parser_t *parser) override
|
|
||||||
{
|
|
||||||
GOptionEntry entries[] =
|
GOptionEntry entries[] =
|
||||||
{
|
{
|
||||||
{"no-glyph-names", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &this->show_glyph_names, "Use glyph indices instead of names", nullptr},
|
{"no-glyph-names", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &this->show_glyph_names, "Use glyph indices instead of names", nullptr},
|
||||||
|
@ -53,13 +54,12 @@ struct shape_closure_consumer_t : option_group_t
|
||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init (hb_buffer_t *buffer_,
|
void init (const font_options_t *font_opts)
|
||||||
const font_options_t *font_opts)
|
|
||||||
{
|
{
|
||||||
glyphs = hb_set_create ();
|
glyphs = hb_set_create ();
|
||||||
font = hb_font_reference (font_opts->get_font ());
|
font = hb_font_reference (font_opts->get_font ());
|
||||||
failed = false;
|
failed = false;
|
||||||
buffer = hb_buffer_reference (buffer_);
|
buffer = hb_buffer_create ();
|
||||||
}
|
}
|
||||||
void consume_line (const char *text,
|
void consume_line (const char *text,
|
||||||
unsigned int text_len,
|
unsigned int text_len,
|
||||||
|
@ -104,16 +104,15 @@ struct shape_closure_consumer_t : option_group_t
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
shape_options_t shaper;
|
shape_options_t shaper;
|
||||||
hb_bool_t show_glyph_names;
|
hb_bool_t show_glyph_names = false;
|
||||||
|
|
||||||
hb_set_t *glyphs;
|
hb_set_t *glyphs = nullptr;
|
||||||
hb_font_t *font;
|
hb_font_t *font = nullptr;
|
||||||
hb_buffer_t *buffer;
|
hb_buffer_t *buffer = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
main_font_text_t<shape_closure_consumer_t, FONT_SIZE_NONE, 0> driver;
|
return main_font_text<shape_closure_consumer_t, font_options_t, text_options_t> (argc, argv);
|
||||||
return driver.main (argc, argv);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,44 +25,48 @@
|
||||||
* Google Author(s): Behdad Esfahbod
|
* Google Author(s): Behdad Esfahbod
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "main-font-text.hh"
|
#include "output-options.hh"
|
||||||
|
#include "font-options.hh"
|
||||||
|
#include "text-options.hh"
|
||||||
#include "shape-consumer.hh"
|
#include "shape-consumer.hh"
|
||||||
|
#include "shape-format.hh"
|
||||||
|
#include "main-font-text.hh"
|
||||||
|
|
||||||
struct output_buffer_t
|
const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_UPEM;
|
||||||
|
const unsigned SUBPIXEL_BITS = 0;
|
||||||
|
|
||||||
|
struct output_buffer_t : output_options_t
|
||||||
{
|
{
|
||||||
output_buffer_t (option_parser_t *parser)
|
void add_options (option_parser_t *parser)
|
||||||
: options (parser, hb_buffer_serialize_list_formats ()),
|
{
|
||||||
format (parser),
|
output_options_t::add_options (parser, hb_buffer_serialize_list_formats ());
|
||||||
gs (nullptr),
|
format.add_options (parser);
|
||||||
line_no (0),
|
}
|
||||||
font (nullptr),
|
|
||||||
output_format (HB_BUFFER_SERIALIZE_FORMAT_INVALID),
|
|
||||||
format_flags (HB_BUFFER_SERIALIZE_FLAG_DEFAULT) {}
|
|
||||||
|
|
||||||
void init (hb_buffer_t *buffer, const font_options_t *font_opts)
|
void init (hb_buffer_t *buffer, const font_options_t *font_opts)
|
||||||
{
|
{
|
||||||
options.get_file_handle ();
|
get_file_handle ();
|
||||||
gs = g_string_new (nullptr);
|
gs = g_string_new (nullptr);
|
||||||
line_no = 0;
|
line_no = 0;
|
||||||
font = hb_font_reference (font_opts->get_font ());
|
font = hb_font_reference (font_opts->get_font ());
|
||||||
|
|
||||||
if (!options.output_format)
|
if (!output_format)
|
||||||
output_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
|
serialize_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
|
||||||
else
|
else
|
||||||
output_format = hb_buffer_serialize_format_from_string (options.output_format, -1);
|
serialize_format = hb_buffer_serialize_format_from_string (output_format, -1);
|
||||||
/* An empty "output_format" parameter basically skips output generating.
|
/* An empty "output_format" parameter basically skips output generating.
|
||||||
* Useful for benchmarking. */
|
* Useful for benchmarking. */
|
||||||
if ((!options.output_format || *options.output_format) &&
|
if ((!output_format || *output_format) &&
|
||||||
!hb_buffer_serialize_format_to_string (output_format))
|
!hb_buffer_serialize_format_to_string (serialize_format))
|
||||||
{
|
{
|
||||||
if (options.explicit_output_format)
|
if (explicit_output_format)
|
||||||
fail (false, "Unknown output format `%s'; supported formats are: %s",
|
fail (false, "Unknown output format `%s'; supported formats are: %s",
|
||||||
options.output_format,
|
output_format,
|
||||||
g_strjoinv ("/", const_cast<char**> (options.supported_formats)));
|
g_strjoinv ("/", const_cast<char**> (hb_buffer_serialize_list_formats ())));
|
||||||
else
|
else
|
||||||
/* Just default to TEXT if not explicitly requested and the
|
/* Just default to TEXT if not explicitly requested and the
|
||||||
* file extension is not recognized. */
|
* file extension is not recognized. */
|
||||||
output_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
|
serialize_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int flags = HB_BUFFER_SERIALIZE_FLAG_DEFAULT;
|
unsigned int flags = HB_BUFFER_SERIALIZE_FLAG_DEFAULT;
|
||||||
|
@ -78,7 +82,7 @@ struct output_buffer_t
|
||||||
flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS;
|
flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS;
|
||||||
if (format.show_flags)
|
if (format.show_flags)
|
||||||
flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS;
|
flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS;
|
||||||
format_flags = (hb_buffer_serialize_flags_t) flags;
|
serialize_flags = (hb_buffer_serialize_flags_t) flags;
|
||||||
|
|
||||||
if (format.trace)
|
if (format.trace)
|
||||||
hb_buffer_set_message_func (buffer, message_func, this, nullptr);
|
hb_buffer_set_message_func (buffer, message_func, this, nullptr);
|
||||||
|
@ -91,13 +95,13 @@ struct output_buffer_t
|
||||||
{
|
{
|
||||||
g_string_set_size (gs, 0);
|
g_string_set_size (gs, 0);
|
||||||
format.serialize_buffer_of_text (buffer, line_no, text, text_len, font, gs);
|
format.serialize_buffer_of_text (buffer, line_no, text, text_len, font, gs);
|
||||||
fprintf (options.fp, "%s", gs->str);
|
fprintf (fp, "%s", gs->str);
|
||||||
}
|
}
|
||||||
void error (const char *message)
|
void error (const char *message)
|
||||||
{
|
{
|
||||||
g_string_set_size (gs, 0);
|
g_string_set_size (gs, 0);
|
||||||
format.serialize_message (line_no, "error", message, gs);
|
format.serialize_message (line_no, "error", message, gs);
|
||||||
fprintf (options.fp, "%s", gs->str);
|
fprintf (fp, "%s", gs->str);
|
||||||
}
|
}
|
||||||
void consume_glyphs (hb_buffer_t *buffer,
|
void consume_glyphs (hb_buffer_t *buffer,
|
||||||
const char *text,
|
const char *text,
|
||||||
|
@ -106,8 +110,8 @@ struct output_buffer_t
|
||||||
{
|
{
|
||||||
g_string_set_size (gs, 0);
|
g_string_set_size (gs, 0);
|
||||||
format.serialize_buffer_of_glyphs (buffer, line_no, text, text_len, font,
|
format.serialize_buffer_of_glyphs (buffer, line_no, text, text_len, font,
|
||||||
output_format, format_flags, gs);
|
serialize_format, serialize_flags, gs);
|
||||||
fprintf (options.fp, "%s", gs->str);
|
fprintf (fp, "%s", gs->str);
|
||||||
}
|
}
|
||||||
void finish (hb_buffer_t *buffer, const font_options_t *font_opts)
|
void finish (hb_buffer_t *buffer, const font_options_t *font_opts)
|
||||||
{
|
{
|
||||||
|
@ -137,29 +141,28 @@ struct output_buffer_t
|
||||||
g_string_set_size (gs, 0);
|
g_string_set_size (gs, 0);
|
||||||
format.serialize_line_no (line_no, gs);
|
format.serialize_line_no (line_no, gs);
|
||||||
g_string_append_printf (gs, "trace: %s buffer: ", message);
|
g_string_append_printf (gs, "trace: %s buffer: ", message);
|
||||||
format.serialize (buffer, font, output_format, format_flags, gs);
|
format.serialize (buffer, font, serialize_format, serialize_flags, gs);
|
||||||
g_string_append_c (gs, '\n');
|
g_string_append_c (gs, '\n');
|
||||||
fprintf (options.fp, "%s", gs->str);
|
fprintf (fp, "%s", gs->str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
output_options_t options;
|
|
||||||
format_options_t format;
|
|
||||||
|
|
||||||
GString *gs;
|
shape_format_options_t format;
|
||||||
unsigned int line_no;
|
|
||||||
hb_font_t *font;
|
GString *gs = nullptr;
|
||||||
hb_buffer_serialize_format_t output_format;
|
unsigned int line_no = 0;
|
||||||
hb_buffer_serialize_flags_t format_flags;
|
hb_font_t *font = nullptr;
|
||||||
|
hb_buffer_serialize_format_t serialize_format = HB_BUFFER_SERIALIZE_FORMAT_INVALID;
|
||||||
|
hb_buffer_serialize_flags_t serialize_flags = HB_BUFFER_SERIALIZE_FLAG_DEFAULT;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <int eol = '\n'>
|
|
||||||
using driver_t = main_font_text_t<shape_consumer_t<output_buffer_t>, FONT_SIZE_UPEM, 0, eol>;
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
auto main_func = main_font_text<shape_consumer_t<output_buffer_t>, font_options_t, text_options_t>;
|
||||||
|
|
||||||
if (argc == 2 && !strcmp (argv[1], "--batch"))
|
if (argc == 2 && !strcmp (argv[1], "--batch"))
|
||||||
{
|
{
|
||||||
unsigned int ret = 0;
|
unsigned int ret = 0;
|
||||||
|
@ -184,13 +187,11 @@ main (int argc, char **argv)
|
||||||
start_offset = argc == 2 && p[0] != '\0' && p[0] != ':' && p[1] == ':' && (p[2] == '\\' || p[2] == '/') ? 2 : 0;
|
start_offset = argc == 2 && p[0] != '\0' && p[0] != ':' && p[1] == ':' && (p[2] == '\\' || p[2] == '/') ? 2 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
driver_t<EOF> driver;
|
ret |= main_func (argc, args, EOF);
|
||||||
ret |= driver.main (argc, args);
|
|
||||||
fflush (stdout);
|
fflush (stdout);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
driver_t<> driver;
|
return main_func (argc, argv, '\n');
|
||||||
return driver.main (argc, argv);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,23 +27,29 @@
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "subset-options.hh"
|
||||||
|
#include "output-options.hh"
|
||||||
|
#include "face-options.hh"
|
||||||
|
#include "text-options.hh"
|
||||||
#include "main-font-text.hh"
|
#include "main-font-text.hh"
|
||||||
#include "hb-subset.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Command line interface to the harfbuzz font subsetter.
|
* Command line interface to the harfbuzz font subsetter.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct subset_consumer_t
|
|
||||||
{
|
|
||||||
subset_consumer_t (option_parser_t *parser)
|
|
||||||
: failed (false), options (parser), subset_options (parser), font (nullptr), input (nullptr) {}
|
|
||||||
|
|
||||||
void init (hb_buffer_t *buffer_,
|
struct subset_consumer_t : subset_options_t, output_options_t
|
||||||
const font_options_t *font_opts)
|
{
|
||||||
|
void add_options (option_parser_t *parser)
|
||||||
{
|
{
|
||||||
font = hb_font_reference (font_opts->get_font ());
|
subset_options_t::add_options (parser);
|
||||||
input = hb_subset_input_reference (subset_options.get_input ());
|
output_options_t::add_options (parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init (const face_options_t *face_opts)
|
||||||
|
{
|
||||||
|
face = hb_face_reference (face_opts->get_face ());
|
||||||
|
input = hb_subset_input_reference (get_input ());
|
||||||
}
|
}
|
||||||
|
|
||||||
void consume_line (const char *text,
|
void consume_line (const char *text,
|
||||||
|
@ -55,7 +61,6 @@ struct subset_consumer_t
|
||||||
hb_set_t *codepoints = hb_subset_input_unicode_set (input);
|
hb_set_t *codepoints = hb_subset_input_unicode_set (input);
|
||||||
if (0 == strcmp (text, "*"))
|
if (0 == strcmp (text, "*"))
|
||||||
{
|
{
|
||||||
hb_face_t *face = hb_font_get_face (font);
|
|
||||||
hb_face_collect_unicodes (face, codepoints);
|
hb_face_collect_unicodes (face, codepoints);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -94,12 +99,11 @@ struct subset_consumer_t
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void finish (const font_options_t *font_opts)
|
void finish (const face_options_t *face_opts)
|
||||||
{
|
{
|
||||||
hb_face_t *face = hb_font_get_face (font);
|
|
||||||
|
|
||||||
hb_face_t *new_face = nullptr;
|
hb_face_t *new_face = nullptr;
|
||||||
for (unsigned i = 0; i < subset_options.num_iterations; i++) {
|
for (unsigned i = 0; i < num_iterations; i++)
|
||||||
|
{
|
||||||
hb_face_destroy (new_face);
|
hb_face_destroy (new_face);
|
||||||
new_face = hb_subset_or_fail (face, input);
|
new_face = hb_subset_or_fail (face, input);
|
||||||
}
|
}
|
||||||
|
@ -108,31 +112,27 @@ struct subset_consumer_t
|
||||||
if (!failed)
|
if (!failed)
|
||||||
{
|
{
|
||||||
hb_blob_t *result = hb_face_reference_blob (new_face);
|
hb_blob_t *result = hb_face_reference_blob (new_face);
|
||||||
write_file (options.output_file, result);
|
write_file (output_file, result);
|
||||||
hb_blob_destroy (result);
|
hb_blob_destroy (result);
|
||||||
}
|
}
|
||||||
|
|
||||||
hb_subset_input_destroy (input);
|
hb_subset_input_destroy (input);
|
||||||
hb_face_destroy (new_face);
|
hb_face_destroy (new_face);
|
||||||
hb_font_destroy (font);
|
hb_face_destroy (face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool failed;
|
bool failed = false;
|
||||||
|
|
||||||
private:
|
hb_face_t *face = nullptr;
|
||||||
output_options_t options;
|
hb_subset_input_t *input = nullptr;
|
||||||
subset_options_t subset_options;
|
|
||||||
hb_font_t *font;
|
|
||||||
hb_subset_input_t *input;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <int eol = '\n'>
|
|
||||||
using driver_t = main_font_text_t<subset_consumer_t, FONT_SIZE_UPEM, 0, eol>;
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
auto main_func = main_font_text<subset_consumer_t, face_options_t, text_options_t>;
|
||||||
|
|
||||||
if (argc == 2 && !strcmp (argv[1], "--batch"))
|
if (argc == 2 && !strcmp (argv[1], "--batch"))
|
||||||
{
|
{
|
||||||
unsigned int ret = 0;
|
unsigned int ret = 0;
|
||||||
|
@ -155,8 +155,7 @@ main (int argc, char **argv)
|
||||||
args[argc++] = p = e;
|
args[argc++] = p = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
driver_t<EOF> driver;
|
int result = main_func (argc, args, EOF);
|
||||||
int result = driver.main (argc, args);
|
|
||||||
fprintf (stdout, result == 0 ? "success\n" : "failure\n");
|
fprintf (stdout, result == 0 ? "success\n" : "failure\n");
|
||||||
fflush (stdout);
|
fflush (stdout);
|
||||||
ret |= result;
|
ret |= result;
|
||||||
|
@ -164,6 +163,5 @@ main (int argc, char **argv)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
driver_t<> driver;
|
return main_func (argc, argv, '\n');
|
||||||
return driver.main (argc, argv);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,16 +25,17 @@
|
||||||
* Google Author(s): Behdad Esfahbod
|
* Google Author(s): Behdad Esfahbod
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "main-font-text.hh"
|
|
||||||
#include "shape-consumer.hh"
|
#include "shape-consumer.hh"
|
||||||
#include "view-cairo.hh"
|
#include "view-cairo.hh"
|
||||||
|
#include "font-options.hh"
|
||||||
|
#include "text-options.hh"
|
||||||
|
#include "main-font-text.hh"
|
||||||
|
|
||||||
#define DEFAULT_FONT_SIZE 256
|
const unsigned DEFAULT_FONT_SIZE = 256;
|
||||||
#define SUBPIXEL_BITS 6
|
const unsigned SUBPIXEL_BITS = 6;
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
main_font_text_t<shape_consumer_t<view_cairo_t>, DEFAULT_FONT_SIZE, SUBPIXEL_BITS> driver;
|
return main_font_text<shape_consumer_t<view_cairo_t>, font_options_t, text_options_t> (argc, argv);
|
||||||
return driver.main (argc, argv);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,202 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2012 Google, Inc.
|
|
||||||
*
|
|
||||||
* This is part of HarfBuzz, a text shaping library.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, without written agreement and without
|
|
||||||
* license or royalty fees, to use, copy, modify, and distribute this
|
|
||||||
* software and its documentation for any purpose, provided that the
|
|
||||||
* above copyright notice and the following two paragraphs appear in
|
|
||||||
* all copies of this software.
|
|
||||||
*
|
|
||||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
|
||||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
|
||||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
* DAMAGE.
|
|
||||||
*
|
|
||||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
|
||||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
|
||||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
|
||||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
|
||||||
*
|
|
||||||
* Google Author(s): Behdad Esfahbod
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "helper-cairo-ansi.hh"
|
|
||||||
#include "options.hh"
|
|
||||||
#include "ansi-print.hh"
|
|
||||||
|
|
||||||
#ifdef HAVE_CHAFA
|
|
||||||
# include <chafa.h>
|
|
||||||
|
|
||||||
/* Similar to ansi-print.cc */
|
|
||||||
# define CELL_W 8
|
|
||||||
# define CELL_H (2 * CELL_W)
|
|
||||||
|
|
||||||
static void
|
|
||||||
chafa_print_image_rgb24 (const void *data, int width, int height, int stride)
|
|
||||||
{
|
|
||||||
ChafaTermInfo *term_info;
|
|
||||||
ChafaSymbolMap *symbol_map;
|
|
||||||
ChafaCanvasConfig *config;
|
|
||||||
ChafaCanvas *canvas;
|
|
||||||
GString *gs;
|
|
||||||
unsigned int cols = (width + CELL_W - 1) / CELL_W;
|
|
||||||
unsigned int rows = (height + CELL_H - 1) / CELL_H;
|
|
||||||
gchar **environ;
|
|
||||||
ChafaCanvasMode mode;
|
|
||||||
ChafaPixelMode pixel_mode;
|
|
||||||
|
|
||||||
/* Adapt to terminal; use sixels if available, and fall back to symbols
|
|
||||||
* with as many colors as are supported */
|
|
||||||
|
|
||||||
environ = g_get_environ ();
|
|
||||||
term_info = chafa_term_db_detect (chafa_term_db_get_default (),
|
|
||||||
environ);
|
|
||||||
|
|
||||||
pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS;
|
|
||||||
|
|
||||||
if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS))
|
|
||||||
{
|
|
||||||
pixel_mode = CHAFA_PIXEL_MODE_SIXELS;
|
|
||||||
mode = CHAFA_CANVAS_MODE_TRUECOLOR;
|
|
||||||
}
|
|
||||||
// else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT))
|
|
||||||
// mode = CHAFA_CANVAS_MODE_TRUECOLOR;
|
|
||||||
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256))
|
|
||||||
mode = CHAFA_CANVAS_MODE_INDEXED_240;
|
|
||||||
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16))
|
|
||||||
mode = CHAFA_CANVAS_MODE_INDEXED_16;
|
|
||||||
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS))
|
|
||||||
mode = CHAFA_CANVAS_MODE_FGBG_BGFG;
|
|
||||||
else
|
|
||||||
mode = CHAFA_CANVAS_MODE_FGBG;
|
|
||||||
|
|
||||||
/* Create the configuration */
|
|
||||||
|
|
||||||
symbol_map = chafa_symbol_map_new ();
|
|
||||||
chafa_symbol_map_add_by_tags (symbol_map,
|
|
||||||
(ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK
|
|
||||||
| CHAFA_SYMBOL_TAG_SPACE));
|
|
||||||
|
|
||||||
config = chafa_canvas_config_new ();
|
|
||||||
chafa_canvas_config_set_canvas_mode (config, mode);
|
|
||||||
chafa_canvas_config_set_pixel_mode (config, pixel_mode);
|
|
||||||
chafa_canvas_config_set_cell_geometry (config, 10, 20);
|
|
||||||
chafa_canvas_config_set_geometry (config, cols, rows);
|
|
||||||
chafa_canvas_config_set_symbol_map (config, symbol_map);
|
|
||||||
chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN);
|
|
||||||
chafa_canvas_config_set_work_factor (config, 1.0f);
|
|
||||||
|
|
||||||
/* Create canvas, draw to it and render output string */
|
|
||||||
|
|
||||||
canvas = chafa_canvas_new (config);
|
|
||||||
chafa_canvas_draw_all_pixels (canvas,
|
|
||||||
/* Cairo byte order is host native */
|
|
||||||
G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
||||||
? CHAFA_PIXEL_BGRA8_PREMULTIPLIED
|
|
||||||
: CHAFA_PIXEL_ARGB8_PREMULTIPLIED,
|
|
||||||
(const guint8 *) data,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
stride);
|
|
||||||
gs = chafa_canvas_print (canvas, term_info);
|
|
||||||
|
|
||||||
/* Print the string */
|
|
||||||
|
|
||||||
fwrite (gs->str, sizeof (char), gs->len, stdout);
|
|
||||||
|
|
||||||
if (pixel_mode != CHAFA_PIXEL_MODE_SIXELS)
|
|
||||||
fputc ('\n', stdout);
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
|
|
||||||
g_string_free (gs, TRUE);
|
|
||||||
chafa_canvas_unref (canvas);
|
|
||||||
chafa_canvas_config_unref (config);
|
|
||||||
chafa_symbol_map_unref (symbol_map);
|
|
||||||
chafa_term_info_unref (term_info);
|
|
||||||
g_strfreev (environ);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* HAVE_CHAFA */
|
|
||||||
|
|
||||||
cairo_status_t
|
|
||||||
helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface,
|
|
||||||
cairo_write_func_t write_func,
|
|
||||||
void *closure)
|
|
||||||
{
|
|
||||||
unsigned int width = cairo_image_surface_get_width (surface);
|
|
||||||
unsigned int height = cairo_image_surface_get_height (surface);
|
|
||||||
if (cairo_image_surface_get_format (surface) != CAIRO_FORMAT_RGB24) {
|
|
||||||
cairo_surface_t *new_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
|
|
||||||
cairo_t *cr = cairo_create (new_surface);
|
|
||||||
if (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_A8) {
|
|
||||||
cairo_set_source_rgb (cr, 0., 0., 0.);
|
|
||||||
cairo_paint (cr);
|
|
||||||
cairo_set_source_rgb (cr, 1., 1., 1.);
|
|
||||||
cairo_mask_surface (cr, surface, 0, 0);
|
|
||||||
} else {
|
|
||||||
cairo_set_source_rgb (cr, 1., 1., 1.);
|
|
||||||
cairo_paint (cr);
|
|
||||||
cairo_set_source_surface (cr, surface, 0, 0);
|
|
||||||
cairo_paint (cr);
|
|
||||||
}
|
|
||||||
cairo_destroy (cr);
|
|
||||||
surface = new_surface;
|
|
||||||
} else
|
|
||||||
cairo_surface_reference (surface);
|
|
||||||
|
|
||||||
unsigned int stride = cairo_image_surface_get_stride (surface);
|
|
||||||
const uint32_t *data = (uint32_t *) (void *) cairo_image_surface_get_data (surface);
|
|
||||||
|
|
||||||
/* We don't have rows to spare on the terminal window...
|
|
||||||
* Find the tight image top/bottom and only print in between. */
|
|
||||||
|
|
||||||
/* Use corner color as background color. */
|
|
||||||
uint32_t bg_color = data ? * (uint32_t *) data : 0;
|
|
||||||
|
|
||||||
/* Drop first row while empty */
|
|
||||||
while (height)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
for (i = 0; i < width; i++)
|
|
||||||
if (data[i] != bg_color)
|
|
||||||
break;
|
|
||||||
if (i < width)
|
|
||||||
break;
|
|
||||||
data += stride / 4;
|
|
||||||
height--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Drop last row while empty */
|
|
||||||
unsigned int orig_height = height;
|
|
||||||
while (height)
|
|
||||||
{
|
|
||||||
const uint32_t *row = data + (height - 1) * stride / 4;
|
|
||||||
unsigned int i;
|
|
||||||
for (i = 0; i < width; i++)
|
|
||||||
if (row[i] != bg_color)
|
|
||||||
break;
|
|
||||||
if (i < width)
|
|
||||||
break;
|
|
||||||
height--;
|
|
||||||
}
|
|
||||||
if (height < orig_height)
|
|
||||||
height++; /* Add one last blank row for padding. */
|
|
||||||
|
|
||||||
if (width && height)
|
|
||||||
{
|
|
||||||
#ifdef HAVE_CHAFA
|
|
||||||
if (true)
|
|
||||||
chafa_print_image_rgb24 (data, width, height, stride);
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
ansi_print_image_rgb24 (data, width, height, stride / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_surface_destroy (surface);
|
|
||||||
return CAIRO_STATUS_SUCCESS;
|
|
||||||
}
|
|
|
@ -31,10 +31,180 @@
|
||||||
|
|
||||||
#include <cairo.h>
|
#include <cairo.h>
|
||||||
|
|
||||||
cairo_status_t
|
#include "ansi-print.hh"
|
||||||
|
|
||||||
|
#ifdef HAVE_CHAFA
|
||||||
|
# include <chafa.h>
|
||||||
|
|
||||||
|
/* Similar to ansi-print.cc */
|
||||||
|
# define CELL_W 8
|
||||||
|
# define CELL_H (2 * CELL_W)
|
||||||
|
|
||||||
|
static void
|
||||||
|
chafa_print_image_rgb24 (const void *data, int width, int height, int stride)
|
||||||
|
{
|
||||||
|
ChafaTermInfo *term_info;
|
||||||
|
ChafaSymbolMap *symbol_map;
|
||||||
|
ChafaCanvasConfig *config;
|
||||||
|
ChafaCanvas *canvas;
|
||||||
|
GString *gs;
|
||||||
|
unsigned int cols = (width + CELL_W - 1) / CELL_W;
|
||||||
|
unsigned int rows = (height + CELL_H - 1) / CELL_H;
|
||||||
|
gchar **environ;
|
||||||
|
ChafaCanvasMode mode;
|
||||||
|
ChafaPixelMode pixel_mode;
|
||||||
|
|
||||||
|
/* Adapt to terminal; use sixels if available, and fall back to symbols
|
||||||
|
* with as many colors as are supported */
|
||||||
|
|
||||||
|
environ = g_get_environ ();
|
||||||
|
term_info = chafa_term_db_detect (chafa_term_db_get_default (),
|
||||||
|
environ);
|
||||||
|
|
||||||
|
pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS;
|
||||||
|
|
||||||
|
if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS))
|
||||||
|
{
|
||||||
|
pixel_mode = CHAFA_PIXEL_MODE_SIXELS;
|
||||||
|
mode = CHAFA_CANVAS_MODE_TRUECOLOR;
|
||||||
|
}
|
||||||
|
// else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT))
|
||||||
|
// mode = CHAFA_CANVAS_MODE_TRUECOLOR;
|
||||||
|
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256))
|
||||||
|
mode = CHAFA_CANVAS_MODE_INDEXED_240;
|
||||||
|
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16))
|
||||||
|
mode = CHAFA_CANVAS_MODE_INDEXED_16;
|
||||||
|
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS))
|
||||||
|
mode = CHAFA_CANVAS_MODE_FGBG_BGFG;
|
||||||
|
else
|
||||||
|
mode = CHAFA_CANVAS_MODE_FGBG;
|
||||||
|
|
||||||
|
/* Create the configuration */
|
||||||
|
|
||||||
|
symbol_map = chafa_symbol_map_new ();
|
||||||
|
chafa_symbol_map_add_by_tags (symbol_map,
|
||||||
|
(ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK
|
||||||
|
| CHAFA_SYMBOL_TAG_SPACE));
|
||||||
|
|
||||||
|
config = chafa_canvas_config_new ();
|
||||||
|
chafa_canvas_config_set_canvas_mode (config, mode);
|
||||||
|
chafa_canvas_config_set_pixel_mode (config, pixel_mode);
|
||||||
|
chafa_canvas_config_set_cell_geometry (config, 10, 20);
|
||||||
|
chafa_canvas_config_set_geometry (config, cols, rows);
|
||||||
|
chafa_canvas_config_set_symbol_map (config, symbol_map);
|
||||||
|
chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN);
|
||||||
|
chafa_canvas_config_set_work_factor (config, 1.0f);
|
||||||
|
|
||||||
|
/* Create canvas, draw to it and render output string */
|
||||||
|
|
||||||
|
canvas = chafa_canvas_new (config);
|
||||||
|
chafa_canvas_draw_all_pixels (canvas,
|
||||||
|
/* Cairo byte order is host native */
|
||||||
|
G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||||
|
? CHAFA_PIXEL_BGRA8_PREMULTIPLIED
|
||||||
|
: CHAFA_PIXEL_ARGB8_PREMULTIPLIED,
|
||||||
|
(const guint8 *) data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
stride);
|
||||||
|
gs = chafa_canvas_print (canvas, term_info);
|
||||||
|
|
||||||
|
/* Print the string */
|
||||||
|
|
||||||
|
fwrite (gs->str, sizeof (char), gs->len, stdout);
|
||||||
|
|
||||||
|
if (pixel_mode != CHAFA_PIXEL_MODE_SIXELS)
|
||||||
|
fputc ('\n', stdout);
|
||||||
|
|
||||||
|
/* Free resources */
|
||||||
|
|
||||||
|
g_string_free (gs, TRUE);
|
||||||
|
chafa_canvas_unref (canvas);
|
||||||
|
chafa_canvas_config_unref (config);
|
||||||
|
chafa_symbol_map_unref (symbol_map);
|
||||||
|
chafa_term_info_unref (term_info);
|
||||||
|
g_strfreev (environ);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_CHAFA */
|
||||||
|
|
||||||
|
static inline cairo_status_t
|
||||||
helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface,
|
helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface,
|
||||||
cairo_write_func_t write_func,
|
cairo_write_func_t write_func,
|
||||||
void *closure);
|
void *closure)
|
||||||
|
{
|
||||||
|
unsigned int width = cairo_image_surface_get_width (surface);
|
||||||
|
unsigned int height = cairo_image_surface_get_height (surface);
|
||||||
|
if (cairo_image_surface_get_format (surface) != CAIRO_FORMAT_RGB24) {
|
||||||
|
cairo_surface_t *new_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
|
||||||
|
cairo_t *cr = cairo_create (new_surface);
|
||||||
|
if (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_A8) {
|
||||||
|
cairo_set_source_rgb (cr, 0., 0., 0.);
|
||||||
|
cairo_paint (cr);
|
||||||
|
cairo_set_source_rgb (cr, 1., 1., 1.);
|
||||||
|
cairo_mask_surface (cr, surface, 0, 0);
|
||||||
|
} else {
|
||||||
|
cairo_set_source_rgb (cr, 1., 1., 1.);
|
||||||
|
cairo_paint (cr);
|
||||||
|
cairo_set_source_surface (cr, surface, 0, 0);
|
||||||
|
cairo_paint (cr);
|
||||||
|
}
|
||||||
|
cairo_destroy (cr);
|
||||||
|
surface = new_surface;
|
||||||
|
} else
|
||||||
|
cairo_surface_reference (surface);
|
||||||
|
|
||||||
|
unsigned int stride = cairo_image_surface_get_stride (surface);
|
||||||
|
const uint32_t *data = (uint32_t *) (void *) cairo_image_surface_get_data (surface);
|
||||||
|
|
||||||
|
/* We don't have rows to spare on the terminal window...
|
||||||
|
* Find the tight image top/bottom and only print in between. */
|
||||||
|
|
||||||
|
/* Use corner color as background color. */
|
||||||
|
uint32_t bg_color = data ? * (uint32_t *) data : 0;
|
||||||
|
|
||||||
|
/* Drop first row while empty */
|
||||||
|
while (height)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < width; i++)
|
||||||
|
if (data[i] != bg_color)
|
||||||
|
break;
|
||||||
|
if (i < width)
|
||||||
|
break;
|
||||||
|
data += stride / 4;
|
||||||
|
height--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop last row while empty */
|
||||||
|
unsigned int orig_height = height;
|
||||||
|
while (height)
|
||||||
|
{
|
||||||
|
const uint32_t *row = data + (height - 1) * stride / 4;
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < width; i++)
|
||||||
|
if (row[i] != bg_color)
|
||||||
|
break;
|
||||||
|
if (i < width)
|
||||||
|
break;
|
||||||
|
height--;
|
||||||
|
}
|
||||||
|
if (height < orig_height)
|
||||||
|
height++; /* Add one last blank row for padding. */
|
||||||
|
|
||||||
|
if (width && height)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_CHAFA
|
||||||
|
if (true)
|
||||||
|
chafa_print_image_rgb24 (data, width, height, stride);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
ansi_print_image_rgb24 (data, width, height, stride / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_surface_destroy (surface);
|
||||||
|
return CAIRO_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,668 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2011 Google, Inc.
|
|
||||||
*
|
|
||||||
* This is part of HarfBuzz, a text shaping library.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, without written agreement and without
|
|
||||||
* license or royalty fees, to use, copy, modify, and distribute this
|
|
||||||
* software and its documentation for any purpose, provided that the
|
|
||||||
* above copyright notice and the following two paragraphs appear in
|
|
||||||
* all copies of this software.
|
|
||||||
*
|
|
||||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
|
||||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
|
||||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
* DAMAGE.
|
|
||||||
*
|
|
||||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
|
||||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
|
||||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
|
||||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
|
||||||
*
|
|
||||||
* Google Author(s): Behdad Esfahbod
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "helper-cairo.hh"
|
|
||||||
|
|
||||||
#include <cairo-ft.h>
|
|
||||||
#include <hb-ft.h>
|
|
||||||
#include FT_MULTIPLE_MASTERS_H
|
|
||||||
|
|
||||||
#include "helper-cairo-ansi.hh"
|
|
||||||
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
||||||
# include <cairo-svg.h>
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_PDF_SURFACE
|
|
||||||
# include <cairo-pdf.h>
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_PS_SURFACE
|
|
||||||
# include <cairo-ps.h>
|
|
||||||
# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
|
|
||||||
# define HAS_EPS 1
|
|
||||||
|
|
||||||
static cairo_surface_t *
|
|
||||||
_cairo_eps_surface_create_for_stream (cairo_write_func_t write_func,
|
|
||||||
void *closure,
|
|
||||||
double width,
|
|
||||||
double height)
|
|
||||||
{
|
|
||||||
cairo_surface_t *surface;
|
|
||||||
|
|
||||||
surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height);
|
|
||||||
cairo_ps_surface_set_eps (surface, true);
|
|
||||||
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
# else
|
|
||||||
# undef HAS_EPS
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static FT_Library ft_library;
|
|
||||||
|
|
||||||
#ifdef HAVE_ATEXIT
|
|
||||||
static inline
|
|
||||||
void free_ft_library ()
|
|
||||||
{
|
|
||||||
FT_Done_FreeType (ft_library);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
cairo_scaled_font_t *
|
|
||||||
helper_cairo_create_scaled_font (const font_options_t *font_opts)
|
|
||||||
{
|
|
||||||
hb_font_t *font = hb_font_reference (font_opts->get_font ());
|
|
||||||
|
|
||||||
cairo_font_face_t *cairo_face;
|
|
||||||
/* We cannot use the FT_Face from hb_font_t, as doing so will confuse hb_font_t because
|
|
||||||
* cairo will reset the face size. As such, create new face...
|
|
||||||
* TODO Perhaps add API to hb-ft to encapsulate this code. */
|
|
||||||
FT_Face ft_face = nullptr;//hb_ft_font_get_face (font);
|
|
||||||
if (!ft_face)
|
|
||||||
{
|
|
||||||
if (!ft_library)
|
|
||||||
{
|
|
||||||
FT_Init_FreeType (&ft_library);
|
|
||||||
#ifdef HAVE_ATEXIT
|
|
||||||
atexit (free_ft_library);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int blob_length;
|
|
||||||
const char *blob_data = hb_blob_get_data (font_opts->blob, &blob_length);
|
|
||||||
|
|
||||||
if (FT_New_Memory_Face (ft_library,
|
|
||||||
(const FT_Byte *) blob_data,
|
|
||||||
blob_length,
|
|
||||||
font_opts->face_index,
|
|
||||||
&ft_face))
|
|
||||||
fail (false, "FT_New_Memory_Face fail");
|
|
||||||
}
|
|
||||||
if (!ft_face)
|
|
||||||
{
|
|
||||||
/* This allows us to get some boxes at least... */
|
|
||||||
cairo_face = cairo_toy_font_face_create ("@cairo:sans",
|
|
||||||
CAIRO_FONT_SLANT_NORMAL,
|
|
||||||
CAIRO_FONT_WEIGHT_NORMAL);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#ifdef HAVE_FT_SET_VAR_BLEND_COORDINATES
|
|
||||||
unsigned int num_coords;
|
|
||||||
const int *coords = hb_font_get_var_coords_normalized (font, &num_coords);
|
|
||||||
if (num_coords)
|
|
||||||
{
|
|
||||||
FT_Fixed *ft_coords = (FT_Fixed *) calloc (num_coords, sizeof (FT_Fixed));
|
|
||||||
if (ft_coords)
|
|
||||||
{
|
|
||||||
for (unsigned int i = 0; i < num_coords; i++)
|
|
||||||
ft_coords[i] = coords[i] << 2;
|
|
||||||
FT_Set_Var_Blend_Coordinates (ft_face, num_coords, ft_coords);
|
|
||||||
free (ft_coords);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, font_opts->ft_load_flags);
|
|
||||||
}
|
|
||||||
cairo_matrix_t ctm, font_matrix;
|
|
||||||
cairo_font_options_t *font_options;
|
|
||||||
|
|
||||||
cairo_matrix_init_identity (&ctm);
|
|
||||||
cairo_matrix_init_scale (&font_matrix,
|
|
||||||
font_opts->font_size_x,
|
|
||||||
font_opts->font_size_y);
|
|
||||||
font_options = cairo_font_options_create ();
|
|
||||||
cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
|
|
||||||
cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
|
|
||||||
|
|
||||||
cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face,
|
|
||||||
&font_matrix,
|
|
||||||
&ctm,
|
|
||||||
font_options);
|
|
||||||
|
|
||||||
cairo_font_options_destroy (font_options);
|
|
||||||
cairo_font_face_destroy (cairo_face);
|
|
||||||
|
|
||||||
static cairo_user_data_key_t key;
|
|
||||||
if (cairo_scaled_font_set_user_data (scaled_font,
|
|
||||||
&key,
|
|
||||||
(void *) font,
|
|
||||||
(cairo_destroy_func_t) hb_font_destroy))
|
|
||||||
hb_font_destroy (font);
|
|
||||||
|
|
||||||
return scaled_font;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font)
|
|
||||||
{
|
|
||||||
bool ret = false;
|
|
||||||
#ifdef FT_HAS_COLOR
|
|
||||||
FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
|
|
||||||
if (ft_face)
|
|
||||||
{
|
|
||||||
if (FT_HAS_COLOR (ft_face))
|
|
||||||
ret = true;
|
|
||||||
cairo_ft_scaled_font_unlock_face (scaled_font);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
enum class image_protocol_t {
|
|
||||||
NONE = 0,
|
|
||||||
ITERM2,
|
|
||||||
KITTY,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct finalize_closure_t {
|
|
||||||
void (*callback)(finalize_closure_t *);
|
|
||||||
cairo_surface_t *surface;
|
|
||||||
cairo_write_func_t write_func;
|
|
||||||
void *closure;
|
|
||||||
image_protocol_t protocol;
|
|
||||||
};
|
|
||||||
static cairo_user_data_key_t finalize_closure_key;
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
finalize_ansi (finalize_closure_t *closure)
|
|
||||||
{
|
|
||||||
cairo_status_t status;
|
|
||||||
status = helper_cairo_surface_write_to_ansi_stream (closure->surface,
|
|
||||||
closure->write_func,
|
|
||||||
closure->closure);
|
|
||||||
if (status != CAIRO_STATUS_SUCCESS)
|
|
||||||
fail (false, "Failed to write output: %s",
|
|
||||||
cairo_status_to_string (status));
|
|
||||||
}
|
|
||||||
|
|
||||||
static cairo_surface_t *
|
|
||||||
_cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func,
|
|
||||||
void *closure,
|
|
||||||
double width,
|
|
||||||
double height,
|
|
||||||
cairo_content_t content,
|
|
||||||
image_protocol_t protocol HB_UNUSED)
|
|
||||||
{
|
|
||||||
cairo_surface_t *surface;
|
|
||||||
int w = ceil (width);
|
|
||||||
int h = ceil (height);
|
|
||||||
|
|
||||||
switch (content) {
|
|
||||||
case CAIRO_CONTENT_ALPHA:
|
|
||||||
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case CAIRO_CONTENT_COLOR:
|
|
||||||
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
|
|
||||||
break;
|
|
||||||
case CAIRO_CONTENT_COLOR_ALPHA:
|
|
||||||
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cairo_status_t status = cairo_surface_status (surface);
|
|
||||||
if (status != CAIRO_STATUS_SUCCESS)
|
|
||||||
fail (false, "Failed to create cairo surface: %s",
|
|
||||||
cairo_status_to_string (status));
|
|
||||||
|
|
||||||
finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1);
|
|
||||||
ansi_closure->callback = finalize_ansi;
|
|
||||||
ansi_closure->surface = surface;
|
|
||||||
ansi_closure->write_func = write_func;
|
|
||||||
ansi_closure->closure = closure;
|
|
||||||
|
|
||||||
if (cairo_surface_set_user_data (surface,
|
|
||||||
&finalize_closure_key,
|
|
||||||
(void *) ansi_closure,
|
|
||||||
(cairo_destroy_func_t) g_free))
|
|
||||||
g_free ((void *) closure);
|
|
||||||
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
|
||||||
|
|
||||||
static cairo_status_t
|
|
||||||
byte_array_write_func (void *closure,
|
|
||||||
const unsigned char *data,
|
|
||||||
unsigned int size)
|
|
||||||
{
|
|
||||||
g_byte_array_append ((GByteArray *) closure, data, size);
|
|
||||||
return CAIRO_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
finalize_png (finalize_closure_t *closure)
|
|
||||||
{
|
|
||||||
cairo_status_t status;
|
|
||||||
GByteArray *bytes = nullptr;
|
|
||||||
GString *string;
|
|
||||||
gchar *base64;
|
|
||||||
size_t base64_len;
|
|
||||||
|
|
||||||
if (closure->protocol == image_protocol_t::NONE)
|
|
||||||
{
|
|
||||||
status = cairo_surface_write_to_png_stream (closure->surface,
|
|
||||||
closure->write_func,
|
|
||||||
closure->closure);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bytes = g_byte_array_new ();
|
|
||||||
status = cairo_surface_write_to_png_stream (closure->surface,
|
|
||||||
byte_array_write_func,
|
|
||||||
bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status != CAIRO_STATUS_SUCCESS)
|
|
||||||
fail (false, "Failed to write output: %s",
|
|
||||||
cairo_status_to_string (status));
|
|
||||||
|
|
||||||
if (closure->protocol == image_protocol_t::NONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
base64 = g_base64_encode (bytes->data, bytes->len);
|
|
||||||
base64_len = strlen (base64);
|
|
||||||
|
|
||||||
string = g_string_new (NULL);
|
|
||||||
if (closure->protocol == image_protocol_t::ITERM2)
|
|
||||||
{
|
|
||||||
/* https://iterm2.com/documentation-images.html */
|
|
||||||
g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n",
|
|
||||||
base64_len, base64);
|
|
||||||
}
|
|
||||||
else if (closure->protocol == image_protocol_t::KITTY)
|
|
||||||
{
|
|
||||||
#define CHUNK_SIZE 4096
|
|
||||||
/* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */
|
|
||||||
for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE)
|
|
||||||
{
|
|
||||||
size_t len = base64_len - pos;
|
|
||||||
|
|
||||||
if (pos == 0)
|
|
||||||
g_string_append (string, "\033_Ga=T,f=100,m=");
|
|
||||||
else
|
|
||||||
g_string_append (string, "\033_Gm=");
|
|
||||||
|
|
||||||
if (len > CHUNK_SIZE)
|
|
||||||
{
|
|
||||||
g_string_append (string, "1;");
|
|
||||||
g_string_append_len (string, base64 + pos, CHUNK_SIZE);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_string_append (string, "0;");
|
|
||||||
g_string_append_len (string, base64 + pos, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_string_append (string, "\033\\");
|
|
||||||
}
|
|
||||||
g_string_append (string, "\n");
|
|
||||||
#undef CHUNK_SIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
closure->write_func (closure->closure, (unsigned char *) string->str, string->len);
|
|
||||||
|
|
||||||
g_byte_array_unref (bytes);
|
|
||||||
g_free (base64);
|
|
||||||
g_string_free (string, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static cairo_surface_t *
|
|
||||||
_cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
|
|
||||||
void *closure,
|
|
||||||
double width,
|
|
||||||
double height,
|
|
||||||
cairo_content_t content,
|
|
||||||
image_protocol_t protocol)
|
|
||||||
{
|
|
||||||
cairo_surface_t *surface;
|
|
||||||
int w = ceil (width);
|
|
||||||
int h = ceil (height);
|
|
||||||
|
|
||||||
switch (content) {
|
|
||||||
case CAIRO_CONTENT_ALPHA:
|
|
||||||
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case CAIRO_CONTENT_COLOR:
|
|
||||||
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
|
|
||||||
break;
|
|
||||||
case CAIRO_CONTENT_COLOR_ALPHA:
|
|
||||||
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cairo_status_t status = cairo_surface_status (surface);
|
|
||||||
if (status != CAIRO_STATUS_SUCCESS)
|
|
||||||
fail (false, "Failed to create cairo surface: %s",
|
|
||||||
cairo_status_to_string (status));
|
|
||||||
|
|
||||||
finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1);
|
|
||||||
png_closure->callback = finalize_png;
|
|
||||||
png_closure->surface = surface;
|
|
||||||
png_closure->write_func = write_func;
|
|
||||||
png_closure->closure = closure;
|
|
||||||
png_closure->protocol = protocol;
|
|
||||||
|
|
||||||
if (cairo_surface_set_user_data (surface,
|
|
||||||
&finalize_closure_key,
|
|
||||||
(void *) png_closure,
|
|
||||||
(cairo_destroy_func_t) g_free))
|
|
||||||
g_free ((void *) closure);
|
|
||||||
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static cairo_status_t
|
|
||||||
stdio_write_func (void *closure,
|
|
||||||
const unsigned char *data,
|
|
||||||
unsigned int size)
|
|
||||||
{
|
|
||||||
FILE *fp = (FILE *) closure;
|
|
||||||
|
|
||||||
while (size) {
|
|
||||||
size_t ret = fwrite (data, 1, size, fp);
|
|
||||||
size -= ret;
|
|
||||||
data += ret;
|
|
||||||
if (size && ferror (fp))
|
|
||||||
fail (false, "Failed to write output: %s", strerror (errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
return CAIRO_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *helper_cairo_supported_formats[] =
|
|
||||||
{
|
|
||||||
"ansi",
|
|
||||||
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
|
||||||
"png",
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
||||||
"svg",
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_PDF_SURFACE
|
|
||||||
"pdf",
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_PS_SURFACE
|
|
||||||
"ps",
|
|
||||||
#ifdef HAS_EPS
|
|
||||||
"eps",
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
cairo_t *
|
|
||||||
helper_cairo_create_context (double w, double h,
|
|
||||||
view_options_t *view_opts,
|
|
||||||
output_options_t *out_opts,
|
|
||||||
cairo_content_t content)
|
|
||||||
{
|
|
||||||
cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
|
|
||||||
void *closure,
|
|
||||||
double width,
|
|
||||||
double height) = nullptr;
|
|
||||||
cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
|
|
||||||
void *closure,
|
|
||||||
double width,
|
|
||||||
double height,
|
|
||||||
cairo_content_t content,
|
|
||||||
image_protocol_t protocol) = nullptr;
|
|
||||||
|
|
||||||
image_protocol_t protocol = image_protocol_t::NONE;
|
|
||||||
const char *extension = out_opts->output_format;
|
|
||||||
if (!extension) {
|
|
||||||
#if HAVE_ISATTY
|
|
||||||
if (isatty (fileno (out_opts->get_file_handle ())))
|
|
||||||
{
|
|
||||||
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
|
||||||
const char *name;
|
|
||||||
/* https://gitlab.com/gnachman/iterm2/-/issues/7154 */
|
|
||||||
if ((name = getenv ("LC_TERMINAL")) != nullptr &&
|
|
||||||
0 == g_ascii_strcasecmp (name, "iTerm2"))
|
|
||||||
{
|
|
||||||
extension = "png";
|
|
||||||
protocol = image_protocol_t::ITERM2;
|
|
||||||
}
|
|
||||||
else if ((name = getenv ("TERM")) != nullptr &&
|
|
||||||
0 == g_ascii_strcasecmp (name, "xterm-kitty"))
|
|
||||||
{
|
|
||||||
extension = "png";
|
|
||||||
protocol = image_protocol_t::KITTY;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
extension = "ansi";
|
|
||||||
#else
|
|
||||||
extension = "ansi";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
|
||||||
extension = "png";
|
|
||||||
#else
|
|
||||||
extension = "ansi";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (0)
|
|
||||||
;
|
|
||||||
else if (0 == g_ascii_strcasecmp (extension, "ansi"))
|
|
||||||
constructor2 = _cairo_ansi_surface_create_for_stream;
|
|
||||||
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
|
||||||
else if (0 == g_ascii_strcasecmp (extension, "png"))
|
|
||||||
constructor2 = _cairo_png_surface_create_for_stream;
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
||||||
else if (0 == g_ascii_strcasecmp (extension, "svg"))
|
|
||||||
constructor = cairo_svg_surface_create_for_stream;
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_PDF_SURFACE
|
|
||||||
else if (0 == g_ascii_strcasecmp (extension, "pdf"))
|
|
||||||
constructor = cairo_pdf_surface_create_for_stream;
|
|
||||||
#endif
|
|
||||||
#ifdef CAIRO_HAS_PS_SURFACE
|
|
||||||
else if (0 == g_ascii_strcasecmp (extension, "ps"))
|
|
||||||
constructor = cairo_ps_surface_create_for_stream;
|
|
||||||
#ifdef HAS_EPS
|
|
||||||
else if (0 == g_ascii_strcasecmp (extension, "eps"))
|
|
||||||
constructor = _cairo_eps_surface_create_for_stream;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
unsigned int fr, fg, fb, fa, br, bg, bb, ba;
|
|
||||||
const char *color;
|
|
||||||
br = bg = bb = 0; ba = 255;
|
|
||||||
color = view_opts->back ? view_opts->back : DEFAULT_BACK;
|
|
||||||
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba);
|
|
||||||
fr = fg = fb = 0; fa = 255;
|
|
||||||
color = view_opts->fore ? view_opts->fore : DEFAULT_FORE;
|
|
||||||
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa);
|
|
||||||
|
|
||||||
if (content == CAIRO_CONTENT_ALPHA)
|
|
||||||
{
|
|
||||||
if (view_opts->annotate ||
|
|
||||||
br != bg || bg != bb ||
|
|
||||||
fr != fg || fg != fb)
|
|
||||||
content = CAIRO_CONTENT_COLOR;
|
|
||||||
}
|
|
||||||
if (ba != 255)
|
|
||||||
content = CAIRO_CONTENT_COLOR_ALPHA;
|
|
||||||
|
|
||||||
cairo_surface_t *surface;
|
|
||||||
FILE *f = out_opts->get_file_handle ();
|
|
||||||
if (constructor)
|
|
||||||
surface = constructor (stdio_write_func, f, w, h);
|
|
||||||
else if (constructor2)
|
|
||||||
surface = constructor2 (stdio_write_func, f, w, h, content, protocol);
|
|
||||||
else
|
|
||||||
fail (false, "Unknown output format `%s'; supported formats are: %s%s",
|
|
||||||
extension,
|
|
||||||
g_strjoinv ("/", const_cast<char**> (helper_cairo_supported_formats)),
|
|
||||||
out_opts->explicit_output_format ? "" :
|
|
||||||
"\nTry setting format using --output-format");
|
|
||||||
|
|
||||||
cairo_t *cr = cairo_create (surface);
|
|
||||||
content = cairo_surface_get_content (surface);
|
|
||||||
|
|
||||||
switch (content) {
|
|
||||||
case CAIRO_CONTENT_ALPHA:
|
|
||||||
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
|
||||||
cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
|
|
||||||
cairo_paint (cr);
|
|
||||||
cairo_set_source_rgba (cr, 1., 1., 1.,
|
|
||||||
(fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case CAIRO_CONTENT_COLOR:
|
|
||||||
case CAIRO_CONTENT_COLOR_ALPHA:
|
|
||||||
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
|
||||||
cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
|
|
||||||
cairo_paint (cr);
|
|
||||||
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
|
|
||||||
cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_surface_destroy (surface);
|
|
||||||
return cr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
helper_cairo_destroy_context (cairo_t *cr)
|
|
||||||
{
|
|
||||||
finalize_closure_t *closure = (finalize_closure_t *)
|
|
||||||
cairo_surface_get_user_data (cairo_get_target (cr),
|
|
||||||
&finalize_closure_key);
|
|
||||||
if (closure)
|
|
||||||
closure->callback (closure);
|
|
||||||
|
|
||||||
cairo_status_t status = cairo_status (cr);
|
|
||||||
if (status != CAIRO_STATUS_SUCCESS)
|
|
||||||
fail (false, "Failed: %s",
|
|
||||||
cairo_status_to_string (status));
|
|
||||||
cairo_destroy (cr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
helper_cairo_line_from_buffer (helper_cairo_line_t *l,
|
|
||||||
hb_buffer_t *buffer,
|
|
||||||
const char *text,
|
|
||||||
unsigned int text_len,
|
|
||||||
int scale_bits,
|
|
||||||
hb_bool_t utf8_clusters)
|
|
||||||
{
|
|
||||||
memset (l, 0, sizeof (*l));
|
|
||||||
|
|
||||||
l->num_glyphs = hb_buffer_get_length (buffer);
|
|
||||||
hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, nullptr);
|
|
||||||
hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, nullptr);
|
|
||||||
l->glyphs = cairo_glyph_allocate (l->num_glyphs + 1);
|
|
||||||
|
|
||||||
if (text) {
|
|
||||||
l->utf8 = g_strndup (text, text_len);
|
|
||||||
l->utf8_len = text_len;
|
|
||||||
l->num_clusters = l->num_glyphs ? 1 : 0;
|
|
||||||
for (unsigned int i = 1; i < l->num_glyphs; i++)
|
|
||||||
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster)
|
|
||||||
l->num_clusters++;
|
|
||||||
l->clusters = cairo_text_cluster_allocate (l->num_clusters);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((l->num_glyphs && !l->glyphs) ||
|
|
||||||
(l->utf8_len && !l->utf8) ||
|
|
||||||
(l->num_clusters && !l->clusters))
|
|
||||||
{
|
|
||||||
l->finish ();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hb_position_t x = 0, y = 0;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < (int) l->num_glyphs; i++)
|
|
||||||
{
|
|
||||||
l->glyphs[i].index = hb_glyph[i].codepoint;
|
|
||||||
l->glyphs[i].x = scalbn ((double) hb_position->x_offset + x, scale_bits);
|
|
||||||
l->glyphs[i].y = scalbn ((double) -hb_position->y_offset + y, scale_bits);
|
|
||||||
x += hb_position->x_advance;
|
|
||||||
y += -hb_position->y_advance;
|
|
||||||
|
|
||||||
hb_position++;
|
|
||||||
}
|
|
||||||
l->glyphs[i].index = -1;
|
|
||||||
l->glyphs[i].x = scalbn ((double) x, scale_bits);
|
|
||||||
l->glyphs[i].y = scalbn ((double) y, scale_bits);
|
|
||||||
|
|
||||||
if (l->num_clusters) {
|
|
||||||
memset ((void *) l->clusters, 0, l->num_clusters * sizeof (l->clusters[0]));
|
|
||||||
hb_bool_t backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer));
|
|
||||||
l->cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0;
|
|
||||||
unsigned int cluster = 0;
|
|
||||||
const char *start = l->utf8, *end;
|
|
||||||
l->clusters[cluster].num_glyphs++;
|
|
||||||
if (backward) {
|
|
||||||
for (i = l->num_glyphs - 2; i >= 0; i--) {
|
|
||||||
if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) {
|
|
||||||
g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster);
|
|
||||||
if (utf8_clusters)
|
|
||||||
end = start + hb_glyph[i].cluster - hb_glyph[i+1].cluster;
|
|
||||||
else
|
|
||||||
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i+1].cluster);
|
|
||||||
l->clusters[cluster].num_bytes = end - start;
|
|
||||||
start = end;
|
|
||||||
cluster++;
|
|
||||||
}
|
|
||||||
l->clusters[cluster].num_glyphs++;
|
|
||||||
}
|
|
||||||
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
|
|
||||||
} else {
|
|
||||||
for (i = 1; i < (int) l->num_glyphs; i++) {
|
|
||||||
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) {
|
|
||||||
g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster);
|
|
||||||
if (utf8_clusters)
|
|
||||||
end = start + hb_glyph[i].cluster - hb_glyph[i-1].cluster;
|
|
||||||
else
|
|
||||||
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i-1].cluster);
|
|
||||||
l->clusters[cluster].num_bytes = end - start;
|
|
||||||
start = end;
|
|
||||||
cluster++;
|
|
||||||
}
|
|
||||||
l->clusters[cluster].num_glyphs++;
|
|
||||||
}
|
|
||||||
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,28 +27,558 @@
|
||||||
#ifndef HELPER_CAIRO_HH
|
#ifndef HELPER_CAIRO_HH
|
||||||
#define HELPER_CAIRO_HH
|
#define HELPER_CAIRO_HH
|
||||||
|
|
||||||
#include "hb.hh"
|
#include "view-options.hh"
|
||||||
#include "options.hh"
|
#include "output-options.hh"
|
||||||
|
|
||||||
#include <cairo.h>
|
#include <cairo-ft.h>
|
||||||
|
#include <hb-ft.h>
|
||||||
|
#include FT_MULTIPLE_MASTERS_H
|
||||||
|
|
||||||
|
#include "helper-cairo-ansi.hh"
|
||||||
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
||||||
|
# include <cairo-svg.h>
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_PDF_SURFACE
|
||||||
|
# include <cairo-pdf.h>
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_PS_SURFACE
|
||||||
|
# include <cairo-ps.h>
|
||||||
|
# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
|
||||||
|
# define HAS_EPS 1
|
||||||
|
|
||||||
|
static cairo_surface_t *
|
||||||
|
_cairo_eps_surface_create_for_stream (cairo_write_func_t write_func,
|
||||||
|
void *closure,
|
||||||
|
double width,
|
||||||
|
double height)
|
||||||
|
{
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
|
||||||
|
surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height);
|
||||||
|
cairo_ps_surface_set_eps (surface, true);
|
||||||
|
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
# else
|
||||||
|
# undef HAS_EPS
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
cairo_scaled_font_t *
|
static FT_Library ft_library;
|
||||||
helper_cairo_create_scaled_font (const font_options_t *font_opts);
|
|
||||||
|
|
||||||
bool
|
#ifdef HAVE_ATEXIT
|
||||||
helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font);
|
static inline
|
||||||
|
void free_ft_library ()
|
||||||
|
{
|
||||||
|
FT_Done_FreeType (ft_library);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
extern const char *helper_cairo_supported_formats[];
|
static inline cairo_scaled_font_t *
|
||||||
|
helper_cairo_create_scaled_font (const font_options_t *font_opts)
|
||||||
|
{
|
||||||
|
hb_font_t *font = hb_font_reference (font_opts->get_font ());
|
||||||
|
|
||||||
cairo_t *
|
cairo_font_face_t *cairo_face;
|
||||||
|
/* We cannot use the FT_Face from hb_font_t, as doing so will confuse hb_font_t because
|
||||||
|
* cairo will reset the face size. As such, create new face...
|
||||||
|
* TODO Perhaps add API to hb-ft to encapsulate this code. */
|
||||||
|
FT_Face ft_face = nullptr;//hb_ft_font_get_face (font);
|
||||||
|
if (!ft_face)
|
||||||
|
{
|
||||||
|
if (!ft_library)
|
||||||
|
{
|
||||||
|
FT_Init_FreeType (&ft_library);
|
||||||
|
#ifdef HAVE_ATEXIT
|
||||||
|
atexit (free_ft_library);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int blob_length;
|
||||||
|
const char *blob_data = hb_blob_get_data (font_opts->get_blob (), &blob_length);
|
||||||
|
|
||||||
|
if (FT_New_Memory_Face (ft_library,
|
||||||
|
(const FT_Byte *) blob_data,
|
||||||
|
blob_length,
|
||||||
|
font_opts->face_index,
|
||||||
|
&ft_face))
|
||||||
|
fail (false, "FT_New_Memory_Face fail");
|
||||||
|
}
|
||||||
|
if (!ft_face)
|
||||||
|
{
|
||||||
|
/* This allows us to get some boxes at least... */
|
||||||
|
cairo_face = cairo_toy_font_face_create ("@cairo:sans",
|
||||||
|
CAIRO_FONT_SLANT_NORMAL,
|
||||||
|
CAIRO_FONT_WEIGHT_NORMAL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef HAVE_FT_SET_VAR_BLEND_COORDINATES
|
||||||
|
unsigned int num_coords;
|
||||||
|
const int *coords = hb_font_get_var_coords_normalized (font, &num_coords);
|
||||||
|
if (num_coords)
|
||||||
|
{
|
||||||
|
FT_Fixed *ft_coords = (FT_Fixed *) calloc (num_coords, sizeof (FT_Fixed));
|
||||||
|
if (ft_coords)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < num_coords; i++)
|
||||||
|
ft_coords[i] = coords[i] << 2;
|
||||||
|
FT_Set_Var_Blend_Coordinates (ft_face, num_coords, ft_coords);
|
||||||
|
free (ft_coords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, font_opts->ft_load_flags);
|
||||||
|
}
|
||||||
|
cairo_matrix_t ctm, font_matrix;
|
||||||
|
cairo_font_options_t *font_options;
|
||||||
|
|
||||||
|
cairo_matrix_init_identity (&ctm);
|
||||||
|
cairo_matrix_init_scale (&font_matrix,
|
||||||
|
font_opts->font_size_x,
|
||||||
|
font_opts->font_size_y);
|
||||||
|
font_options = cairo_font_options_create ();
|
||||||
|
cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
|
||||||
|
cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
|
||||||
|
|
||||||
|
cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face,
|
||||||
|
&font_matrix,
|
||||||
|
&ctm,
|
||||||
|
font_options);
|
||||||
|
|
||||||
|
cairo_font_options_destroy (font_options);
|
||||||
|
cairo_font_face_destroy (cairo_face);
|
||||||
|
|
||||||
|
static cairo_user_data_key_t key;
|
||||||
|
if (cairo_scaled_font_set_user_data (scaled_font,
|
||||||
|
&key,
|
||||||
|
(void *) font,
|
||||||
|
(cairo_destroy_func_t) hb_font_destroy))
|
||||||
|
hb_font_destroy (font);
|
||||||
|
|
||||||
|
return scaled_font;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
#ifdef FT_HAS_COLOR
|
||||||
|
FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
|
||||||
|
if (ft_face)
|
||||||
|
{
|
||||||
|
if (FT_HAS_COLOR (ft_face))
|
||||||
|
ret = true;
|
||||||
|
cairo_ft_scaled_font_unlock_face (scaled_font);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class image_protocol_t {
|
||||||
|
NONE = 0,
|
||||||
|
ITERM2,
|
||||||
|
KITTY,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct finalize_closure_t {
|
||||||
|
void (*callback)(finalize_closure_t *);
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
cairo_write_func_t write_func;
|
||||||
|
void *closure;
|
||||||
|
image_protocol_t protocol;
|
||||||
|
};
|
||||||
|
static cairo_user_data_key_t finalize_closure_key;
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
finalize_ansi (finalize_closure_t *closure)
|
||||||
|
{
|
||||||
|
cairo_status_t status;
|
||||||
|
status = helper_cairo_surface_write_to_ansi_stream (closure->surface,
|
||||||
|
closure->write_func,
|
||||||
|
closure->closure);
|
||||||
|
if (status != CAIRO_STATUS_SUCCESS)
|
||||||
|
fail (false, "Failed to write output: %s",
|
||||||
|
cairo_status_to_string (status));
|
||||||
|
}
|
||||||
|
|
||||||
|
static cairo_surface_t *
|
||||||
|
_cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func,
|
||||||
|
void *closure,
|
||||||
|
double width,
|
||||||
|
double height,
|
||||||
|
cairo_content_t content,
|
||||||
|
image_protocol_t protocol HB_UNUSED)
|
||||||
|
{
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
int w = ceil (width);
|
||||||
|
int h = ceil (height);
|
||||||
|
|
||||||
|
switch (content) {
|
||||||
|
case CAIRO_CONTENT_ALPHA:
|
||||||
|
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case CAIRO_CONTENT_COLOR:
|
||||||
|
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
|
||||||
|
break;
|
||||||
|
case CAIRO_CONTENT_COLOR_ALPHA:
|
||||||
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cairo_status_t status = cairo_surface_status (surface);
|
||||||
|
if (status != CAIRO_STATUS_SUCCESS)
|
||||||
|
fail (false, "Failed to create cairo surface: %s",
|
||||||
|
cairo_status_to_string (status));
|
||||||
|
|
||||||
|
finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1);
|
||||||
|
ansi_closure->callback = finalize_ansi;
|
||||||
|
ansi_closure->surface = surface;
|
||||||
|
ansi_closure->write_func = write_func;
|
||||||
|
ansi_closure->closure = closure;
|
||||||
|
|
||||||
|
if (cairo_surface_set_user_data (surface,
|
||||||
|
&finalize_closure_key,
|
||||||
|
(void *) ansi_closure,
|
||||||
|
(cairo_destroy_func_t) g_free))
|
||||||
|
g_free ((void *) closure);
|
||||||
|
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
||||||
|
|
||||||
|
static cairo_status_t
|
||||||
|
byte_array_write_func (void *closure,
|
||||||
|
const unsigned char *data,
|
||||||
|
unsigned int size)
|
||||||
|
{
|
||||||
|
g_byte_array_append ((GByteArray *) closure, data, size);
|
||||||
|
return CAIRO_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
finalize_png (finalize_closure_t *closure)
|
||||||
|
{
|
||||||
|
cairo_status_t status;
|
||||||
|
GByteArray *bytes = nullptr;
|
||||||
|
GString *string;
|
||||||
|
gchar *base64;
|
||||||
|
size_t base64_len;
|
||||||
|
|
||||||
|
if (closure->protocol == image_protocol_t::NONE)
|
||||||
|
{
|
||||||
|
status = cairo_surface_write_to_png_stream (closure->surface,
|
||||||
|
closure->write_func,
|
||||||
|
closure->closure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytes = g_byte_array_new ();
|
||||||
|
status = cairo_surface_write_to_png_stream (closure->surface,
|
||||||
|
byte_array_write_func,
|
||||||
|
bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != CAIRO_STATUS_SUCCESS)
|
||||||
|
fail (false, "Failed to write output: %s",
|
||||||
|
cairo_status_to_string (status));
|
||||||
|
|
||||||
|
if (closure->protocol == image_protocol_t::NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base64 = g_base64_encode (bytes->data, bytes->len);
|
||||||
|
base64_len = strlen (base64);
|
||||||
|
|
||||||
|
string = g_string_new (NULL);
|
||||||
|
if (closure->protocol == image_protocol_t::ITERM2)
|
||||||
|
{
|
||||||
|
/* https://iterm2.com/documentation-images.html */
|
||||||
|
g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n",
|
||||||
|
base64_len, base64);
|
||||||
|
}
|
||||||
|
else if (closure->protocol == image_protocol_t::KITTY)
|
||||||
|
{
|
||||||
|
#define CHUNK_SIZE 4096
|
||||||
|
/* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */
|
||||||
|
for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE)
|
||||||
|
{
|
||||||
|
size_t len = base64_len - pos;
|
||||||
|
|
||||||
|
if (pos == 0)
|
||||||
|
g_string_append (string, "\033_Ga=T,f=100,m=");
|
||||||
|
else
|
||||||
|
g_string_append (string, "\033_Gm=");
|
||||||
|
|
||||||
|
if (len > CHUNK_SIZE)
|
||||||
|
{
|
||||||
|
g_string_append (string, "1;");
|
||||||
|
g_string_append_len (string, base64 + pos, CHUNK_SIZE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_string_append (string, "0;");
|
||||||
|
g_string_append_len (string, base64 + pos, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_string_append (string, "\033\\");
|
||||||
|
}
|
||||||
|
g_string_append (string, "\n");
|
||||||
|
#undef CHUNK_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
closure->write_func (closure->closure, (unsigned char *) string->str, string->len);
|
||||||
|
|
||||||
|
g_byte_array_unref (bytes);
|
||||||
|
g_free (base64);
|
||||||
|
g_string_free (string, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static cairo_surface_t *
|
||||||
|
_cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
|
||||||
|
void *closure,
|
||||||
|
double width,
|
||||||
|
double height,
|
||||||
|
cairo_content_t content,
|
||||||
|
image_protocol_t protocol)
|
||||||
|
{
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
int w = ceil (width);
|
||||||
|
int h = ceil (height);
|
||||||
|
|
||||||
|
switch (content) {
|
||||||
|
case CAIRO_CONTENT_ALPHA:
|
||||||
|
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case CAIRO_CONTENT_COLOR:
|
||||||
|
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
|
||||||
|
break;
|
||||||
|
case CAIRO_CONTENT_COLOR_ALPHA:
|
||||||
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cairo_status_t status = cairo_surface_status (surface);
|
||||||
|
if (status != CAIRO_STATUS_SUCCESS)
|
||||||
|
fail (false, "Failed to create cairo surface: %s",
|
||||||
|
cairo_status_to_string (status));
|
||||||
|
|
||||||
|
finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1);
|
||||||
|
png_closure->callback = finalize_png;
|
||||||
|
png_closure->surface = surface;
|
||||||
|
png_closure->write_func = write_func;
|
||||||
|
png_closure->closure = closure;
|
||||||
|
png_closure->protocol = protocol;
|
||||||
|
|
||||||
|
if (cairo_surface_set_user_data (surface,
|
||||||
|
&finalize_closure_key,
|
||||||
|
(void *) png_closure,
|
||||||
|
(cairo_destroy_func_t) g_free))
|
||||||
|
g_free ((void *) closure);
|
||||||
|
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static cairo_status_t
|
||||||
|
stdio_write_func (void *closure,
|
||||||
|
const unsigned char *data,
|
||||||
|
unsigned int size)
|
||||||
|
{
|
||||||
|
FILE *fp = (FILE *) closure;
|
||||||
|
|
||||||
|
while (size) {
|
||||||
|
size_t ret = fwrite (data, 1, size, fp);
|
||||||
|
size -= ret;
|
||||||
|
data += ret;
|
||||||
|
if (size && ferror (fp))
|
||||||
|
fail (false, "Failed to write output: %s", strerror (errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CAIRO_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *helper_cairo_supported_formats[] =
|
||||||
|
{
|
||||||
|
"ansi",
|
||||||
|
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
||||||
|
"png",
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
||||||
|
"svg",
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_PDF_SURFACE
|
||||||
|
"pdf",
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_PS_SURFACE
|
||||||
|
"ps",
|
||||||
|
#ifdef HAS_EPS
|
||||||
|
"eps",
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline cairo_t *
|
||||||
helper_cairo_create_context (double w, double h,
|
helper_cairo_create_context (double w, double h,
|
||||||
view_options_t *view_opts,
|
view_options_t *view_opts,
|
||||||
output_options_t *out_opts,
|
output_options_t *out_opts,
|
||||||
cairo_content_t content);
|
cairo_content_t content)
|
||||||
|
{
|
||||||
|
cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
|
||||||
|
void *closure,
|
||||||
|
double width,
|
||||||
|
double height) = nullptr;
|
||||||
|
cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
|
||||||
|
void *closure,
|
||||||
|
double width,
|
||||||
|
double height,
|
||||||
|
cairo_content_t content,
|
||||||
|
image_protocol_t protocol) = nullptr;
|
||||||
|
|
||||||
void
|
image_protocol_t protocol = image_protocol_t::NONE;
|
||||||
helper_cairo_destroy_context (cairo_t *cr);
|
const char *extension = out_opts->output_format;
|
||||||
|
if (!extension) {
|
||||||
|
#if HAVE_ISATTY
|
||||||
|
if (isatty (fileno (out_opts->get_file_handle ())))
|
||||||
|
{
|
||||||
|
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
||||||
|
const char *name;
|
||||||
|
/* https://gitlab.com/gnachman/iterm2/-/issues/7154 */
|
||||||
|
if ((name = getenv ("LC_TERMINAL")) != nullptr &&
|
||||||
|
0 == g_ascii_strcasecmp (name, "iTerm2"))
|
||||||
|
{
|
||||||
|
extension = "png";
|
||||||
|
protocol = image_protocol_t::ITERM2;
|
||||||
|
}
|
||||||
|
else if ((name = getenv ("TERM")) != nullptr &&
|
||||||
|
0 == g_ascii_strcasecmp (name, "xterm-kitty"))
|
||||||
|
{
|
||||||
|
extension = "png";
|
||||||
|
protocol = image_protocol_t::KITTY;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
extension = "ansi";
|
||||||
|
#else
|
||||||
|
extension = "ansi";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
||||||
|
extension = "png";
|
||||||
|
#else
|
||||||
|
extension = "ansi";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0)
|
||||||
|
;
|
||||||
|
else if (0 == g_ascii_strcasecmp (extension, "ansi"))
|
||||||
|
constructor2 = _cairo_ansi_surface_create_for_stream;
|
||||||
|
#ifdef CAIRO_HAS_PNG_FUNCTIONS
|
||||||
|
else if (0 == g_ascii_strcasecmp (extension, "png"))
|
||||||
|
constructor2 = _cairo_png_surface_create_for_stream;
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
||||||
|
else if (0 == g_ascii_strcasecmp (extension, "svg"))
|
||||||
|
constructor = cairo_svg_surface_create_for_stream;
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_PDF_SURFACE
|
||||||
|
else if (0 == g_ascii_strcasecmp (extension, "pdf"))
|
||||||
|
constructor = cairo_pdf_surface_create_for_stream;
|
||||||
|
#endif
|
||||||
|
#ifdef CAIRO_HAS_PS_SURFACE
|
||||||
|
else if (0 == g_ascii_strcasecmp (extension, "ps"))
|
||||||
|
constructor = cairo_ps_surface_create_for_stream;
|
||||||
|
#ifdef HAS_EPS
|
||||||
|
else if (0 == g_ascii_strcasecmp (extension, "eps"))
|
||||||
|
constructor = _cairo_eps_surface_create_for_stream;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
unsigned int fr, fg, fb, fa, br, bg, bb, ba;
|
||||||
|
const char *color;
|
||||||
|
br = bg = bb = 0; ba = 255;
|
||||||
|
color = view_opts->back ? view_opts->back : DEFAULT_BACK;
|
||||||
|
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba);
|
||||||
|
fr = fg = fb = 0; fa = 255;
|
||||||
|
color = view_opts->fore ? view_opts->fore : DEFAULT_FORE;
|
||||||
|
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa);
|
||||||
|
|
||||||
|
if (content == CAIRO_CONTENT_ALPHA)
|
||||||
|
{
|
||||||
|
if (view_opts->annotate ||
|
||||||
|
br != bg || bg != bb ||
|
||||||
|
fr != fg || fg != fb)
|
||||||
|
content = CAIRO_CONTENT_COLOR;
|
||||||
|
}
|
||||||
|
if (ba != 255)
|
||||||
|
content = CAIRO_CONTENT_COLOR_ALPHA;
|
||||||
|
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
FILE *f = out_opts->get_file_handle ();
|
||||||
|
if (constructor)
|
||||||
|
surface = constructor (stdio_write_func, f, w, h);
|
||||||
|
else if (constructor2)
|
||||||
|
surface = constructor2 (stdio_write_func, f, w, h, content, protocol);
|
||||||
|
else
|
||||||
|
fail (false, "Unknown output format `%s'; supported formats are: %s%s",
|
||||||
|
extension,
|
||||||
|
g_strjoinv ("/", const_cast<char**> (helper_cairo_supported_formats)),
|
||||||
|
out_opts->explicit_output_format ? "" :
|
||||||
|
"\nTry setting format using --output-format");
|
||||||
|
|
||||||
|
cairo_t *cr = cairo_create (surface);
|
||||||
|
content = cairo_surface_get_content (surface);
|
||||||
|
|
||||||
|
switch (content) {
|
||||||
|
case CAIRO_CONTENT_ALPHA:
|
||||||
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
||||||
|
cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
|
||||||
|
cairo_paint (cr);
|
||||||
|
cairo_set_source_rgba (cr, 1., 1., 1.,
|
||||||
|
(fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case CAIRO_CONTENT_COLOR:
|
||||||
|
case CAIRO_CONTENT_COLOR_ALPHA:
|
||||||
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
||||||
|
cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
|
||||||
|
cairo_paint (cr);
|
||||||
|
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
|
||||||
|
cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_surface_destroy (surface);
|
||||||
|
return cr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
helper_cairo_destroy_context (cairo_t *cr)
|
||||||
|
{
|
||||||
|
finalize_closure_t *closure = (finalize_closure_t *)
|
||||||
|
cairo_surface_get_user_data (cairo_get_target (cr),
|
||||||
|
&finalize_closure_key);
|
||||||
|
if (closure)
|
||||||
|
closure->callback (closure);
|
||||||
|
|
||||||
|
cairo_status_t status = cairo_status (cr);
|
||||||
|
if (status != CAIRO_STATUS_SUCCESS)
|
||||||
|
fail (false, "Failed: %s",
|
||||||
|
cairo_status_to_string (status));
|
||||||
|
cairo_destroy (cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct helper_cairo_line_t {
|
struct helper_cairo_line_t {
|
||||||
|
@ -75,12 +605,94 @@ struct helper_cairo_line_t {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
static inline void
|
||||||
helper_cairo_line_from_buffer (helper_cairo_line_t *l,
|
helper_cairo_line_from_buffer (helper_cairo_line_t *l,
|
||||||
hb_buffer_t *buffer,
|
hb_buffer_t *buffer,
|
||||||
const char *text,
|
const char *text,
|
||||||
unsigned int text_len,
|
unsigned int text_len,
|
||||||
int scale_bits,
|
int scale_bits,
|
||||||
hb_bool_t utf8_clusters);
|
hb_bool_t utf8_clusters)
|
||||||
|
{
|
||||||
|
memset (l, 0, sizeof (*l));
|
||||||
|
|
||||||
|
l->num_glyphs = hb_buffer_get_length (buffer);
|
||||||
|
hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, nullptr);
|
||||||
|
hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, nullptr);
|
||||||
|
l->glyphs = cairo_glyph_allocate (l->num_glyphs + 1);
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
l->utf8 = g_strndup (text, text_len);
|
||||||
|
l->utf8_len = text_len;
|
||||||
|
l->num_clusters = l->num_glyphs ? 1 : 0;
|
||||||
|
for (unsigned int i = 1; i < l->num_glyphs; i++)
|
||||||
|
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster)
|
||||||
|
l->num_clusters++;
|
||||||
|
l->clusters = cairo_text_cluster_allocate (l->num_clusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((l->num_glyphs && !l->glyphs) ||
|
||||||
|
(l->utf8_len && !l->utf8) ||
|
||||||
|
(l->num_clusters && !l->clusters))
|
||||||
|
{
|
||||||
|
l->finish ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hb_position_t x = 0, y = 0;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < (int) l->num_glyphs; i++)
|
||||||
|
{
|
||||||
|
l->glyphs[i].index = hb_glyph[i].codepoint;
|
||||||
|
l->glyphs[i].x = scalbn ((double) hb_position->x_offset + x, scale_bits);
|
||||||
|
l->glyphs[i].y = scalbn ((double) -hb_position->y_offset + y, scale_bits);
|
||||||
|
x += hb_position->x_advance;
|
||||||
|
y += -hb_position->y_advance;
|
||||||
|
|
||||||
|
hb_position++;
|
||||||
|
}
|
||||||
|
l->glyphs[i].index = -1;
|
||||||
|
l->glyphs[i].x = scalbn ((double) x, scale_bits);
|
||||||
|
l->glyphs[i].y = scalbn ((double) y, scale_bits);
|
||||||
|
|
||||||
|
if (l->num_clusters) {
|
||||||
|
memset ((void *) l->clusters, 0, l->num_clusters * sizeof (l->clusters[0]));
|
||||||
|
hb_bool_t backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer));
|
||||||
|
l->cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0;
|
||||||
|
unsigned int cluster = 0;
|
||||||
|
const char *start = l->utf8, *end;
|
||||||
|
l->clusters[cluster].num_glyphs++;
|
||||||
|
if (backward) {
|
||||||
|
for (i = l->num_glyphs - 2; i >= 0; i--) {
|
||||||
|
if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) {
|
||||||
|
g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster);
|
||||||
|
if (utf8_clusters)
|
||||||
|
end = start + hb_glyph[i].cluster - hb_glyph[i+1].cluster;
|
||||||
|
else
|
||||||
|
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i+1].cluster);
|
||||||
|
l->clusters[cluster].num_bytes = end - start;
|
||||||
|
start = end;
|
||||||
|
cluster++;
|
||||||
|
}
|
||||||
|
l->clusters[cluster].num_glyphs++;
|
||||||
|
}
|
||||||
|
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
|
||||||
|
} else {
|
||||||
|
for (i = 1; i < (int) l->num_glyphs; i++) {
|
||||||
|
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) {
|
||||||
|
g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster);
|
||||||
|
if (utf8_clusters)
|
||||||
|
end = start + hb_glyph[i].cluster - hb_glyph[i-1].cluster;
|
||||||
|
else
|
||||||
|
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i-1].cluster);
|
||||||
|
l->clusters[cluster].num_bytes = end - start;
|
||||||
|
start = end;
|
||||||
|
cluster++;
|
||||||
|
}
|
||||||
|
l->clusters[cluster].num_glyphs++;
|
||||||
|
}
|
||||||
|
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -27,38 +27,23 @@
|
||||||
#ifndef HB_MAIN_FONT_TEXT_HH
|
#ifndef HB_MAIN_FONT_TEXT_HH
|
||||||
#define HB_MAIN_FONT_TEXT_HH
|
#define HB_MAIN_FONT_TEXT_HH
|
||||||
|
|
||||||
#include "hb.hh"
|
|
||||||
#include "options.hh"
|
#include "options.hh"
|
||||||
|
|
||||||
/* main() body for utilities taking font and processing text.*/
|
/* main() body for utilities taking font and processing text.*/
|
||||||
|
|
||||||
static char *
|
template <typename consumer_t, typename font_options_t, typename text_options_t>
|
||||||
locale_to_utf8 (char *s)
|
int
|
||||||
|
main_font_text (int argc, char **argv, int eol = '\n')
|
||||||
{
|
{
|
||||||
char *t;
|
|
||||||
GError *error = nullptr;
|
|
||||||
|
|
||||||
t = g_locale_to_utf8 (s, -1, nullptr, nullptr, &error);
|
font_options_t font_opts;
|
||||||
if (!t)
|
text_options_t input;
|
||||||
{
|
consumer_t consumer;
|
||||||
fail (true, "Failed converting text to UTF-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
return t;
|
option_parser_t options ("[FONT-FILE] [TEXT]");
|
||||||
}
|
font_opts.add_options (&options);
|
||||||
|
input.add_options (&options);
|
||||||
template <typename consumer_t, int default_font_size, int subpixel_bits, int eol = '\n'>
|
consumer.add_options (&options);
|
||||||
struct main_font_text_t
|
|
||||||
{
|
|
||||||
main_font_text_t ()
|
|
||||||
: options ("[FONT-FILE] [TEXT]"),
|
|
||||||
font_opts (&options, default_font_size, subpixel_bits),
|
|
||||||
input (&options),
|
|
||||||
consumer (&options) {}
|
|
||||||
|
|
||||||
int
|
|
||||||
main (int argc, char **argv)
|
|
||||||
{
|
|
||||||
options.parse (&argc, &argv);
|
options.parse (&argc, &argv);
|
||||||
|
|
||||||
argc--, argv++;
|
argc--, argv++;
|
||||||
|
@ -71,9 +56,7 @@ struct main_font_text_t
|
||||||
if (!input.text && !input.text_file)
|
if (!input.text && !input.text_file)
|
||||||
input.text_file = g_strdup ("-");
|
input.text_file = g_strdup ("-");
|
||||||
|
|
||||||
hb_buffer_t *buffer = hb_buffer_create ();
|
consumer.init (&font_opts);
|
||||||
consumer.init (buffer, &font_opts);
|
|
||||||
hb_buffer_destroy (buffer);
|
|
||||||
|
|
||||||
unsigned int text_len;
|
unsigned int text_len;
|
||||||
const char *text;
|
const char *text;
|
||||||
|
@ -83,14 +66,6 @@ struct main_font_text_t
|
||||||
consumer.finish (&font_opts);
|
consumer.finish (&font_opts);
|
||||||
|
|
||||||
return consumer.failed ? 1 : 0;
|
return consumer.failed ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
|
||||||
option_parser_t options;
|
|
||||||
font_options_t font_opts;
|
|
||||||
text_options_t input;
|
|
||||||
consumer_t consumer;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,17 @@
|
||||||
hb_view_sources = [
|
hb_view_sources = [
|
||||||
'hb-view.cc',
|
'hb-view.cc',
|
||||||
'options.cc',
|
|
||||||
'ansi-print.cc',
|
|
||||||
'helper-cairo.cc',
|
|
||||||
'helper-cairo-ansi.cc',
|
|
||||||
'view-cairo.cc',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
hb_shape_sources = [
|
hb_shape_sources = [
|
||||||
'hb-shape.cc',
|
'hb-shape.cc',
|
||||||
'options.cc',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
hb_ot_shape_closure_sources = [
|
hb_ot_shape_closure_sources = [
|
||||||
'hb-ot-shape-closure.cc',
|
'hb-ot-shape-closure.cc',
|
||||||
'options.cc',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
hb_subset_cli_sources = [
|
hb_subset_cli_sources = [
|
||||||
'hb-subset.cc',
|
'hb-subset.cc',
|
||||||
'options.cc',
|
|
||||||
'options-subset.cc',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
util_deps = [freetype_dep, cairo_dep, cairo_ft_dep, glib_dep]
|
util_deps = [freetype_dep, cairo_dep, cairo_ft_dep, glib_dep]
|
||||||
|
|
1024
util/options.cc
1024
util/options.cc
File diff suppressed because it is too large
Load Diff
729
util/options.hh
729
util/options.hh
|
@ -28,7 +28,6 @@
|
||||||
#define OPTIONS_HH
|
#define OPTIONS_HH
|
||||||
|
|
||||||
#include "hb.hh"
|
#include "hb.hh"
|
||||||
#include "hb-subset.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
@ -51,28 +50,49 @@
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gprintf.h>
|
#include <glib/gprintf.h>
|
||||||
|
|
||||||
void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN G_GNUC_PRINTF (2, 3);
|
|
||||||
|
|
||||||
struct option_group_t
|
static inline void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN G_GNUC_PRINTF (2, 3);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
fail (hb_bool_t suggest_help, const char *format, ...)
|
||||||
{
|
{
|
||||||
virtual ~option_group_t () {}
|
const char *msg;
|
||||||
|
|
||||||
virtual void add_options (struct option_parser_t *parser) = 0;
|
va_list vap;
|
||||||
|
va_start (vap, format);
|
||||||
|
msg = g_strdup_vprintf (format, vap);
|
||||||
|
va_end (vap);
|
||||||
|
const char *prgname = g_get_prgname ();
|
||||||
|
g_printerr ("%s: %s\n", prgname, msg);
|
||||||
|
if (suggest_help)
|
||||||
|
g_printerr ("Try `%s --help' for more information.\n", prgname);
|
||||||
|
|
||||||
virtual void pre_parse (GError **error G_GNUC_UNUSED) {}
|
exit (1);
|
||||||
virtual void post_parse (GError **error G_GNUC_UNUSED) {}
|
}
|
||||||
};
|
|
||||||
|
static inline char *
|
||||||
|
locale_to_utf8 (char *s)
|
||||||
|
{
|
||||||
|
char *t;
|
||||||
|
GError *error = nullptr;
|
||||||
|
|
||||||
|
t = g_locale_to_utf8 (s, -1, nullptr, nullptr, &error);
|
||||||
|
if (!t)
|
||||||
|
{
|
||||||
|
fail (true, "Failed converting text to UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct option_parser_t
|
struct option_parser_t
|
||||||
{
|
{
|
||||||
option_parser_t (const char *usage)
|
option_parser_t (const char *usage)
|
||||||
|
: usage_str (usage),
|
||||||
|
context (g_option_context_new (usage)),
|
||||||
|
to_free (g_ptr_array_new ())
|
||||||
{
|
{
|
||||||
memset (this, 0, sizeof (*this));
|
|
||||||
usage_str = usage;
|
|
||||||
context = g_option_context_new (usage);
|
|
||||||
to_free = g_ptr_array_new ();
|
|
||||||
|
|
||||||
add_main_options ();
|
add_main_options ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +107,36 @@ struct option_parser_t
|
||||||
|
|
||||||
void add_main_options ();
|
void add_main_options ();
|
||||||
|
|
||||||
|
static void
|
||||||
|
post_parse_ (void *thiz, GError **error) {}
|
||||||
|
template <typename Type>
|
||||||
|
static auto
|
||||||
|
post_parse_ (Type *thiz, GError **error) -> decltype (thiz->post_parse (error))
|
||||||
|
{ thiz->post_parse (error); }
|
||||||
|
template <typename Type>
|
||||||
|
static gboolean
|
||||||
|
post_parse (GOptionContext *context G_GNUC_UNUSED,
|
||||||
|
GOptionGroup *group G_GNUC_UNUSED,
|
||||||
|
gpointer data,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
option_parser_t::post_parse_ (static_cast<Type *> (data), error);
|
||||||
|
return !*error;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
void add_group (GOptionEntry *entries,
|
void add_group (GOptionEntry *entries,
|
||||||
const gchar *name,
|
const gchar *name,
|
||||||
const gchar *description,
|
const gchar *description,
|
||||||
const gchar *help_description,
|
const gchar *help_description,
|
||||||
option_group_t *option_group);
|
Type *closure)
|
||||||
|
{
|
||||||
|
GOptionGroup *group = g_option_group_new (name, description, help_description,
|
||||||
|
static_cast<gpointer>(closure), nullptr);
|
||||||
|
g_option_group_add_entries (group, entries);
|
||||||
|
g_option_group_set_parse_hooks (group, nullptr, post_parse<Type>);
|
||||||
|
g_option_context_add_group (context, group);
|
||||||
|
}
|
||||||
|
|
||||||
void free_later (char *p) {
|
void free_later (char *p) {
|
||||||
g_ptr_array_add (to_free, p);
|
g_ptr_array_add (to_free, p);
|
||||||
|
@ -111,628 +156,68 @@ struct option_parser_t
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#define DEFAULT_MARGIN 16
|
static inline gchar *
|
||||||
#define DEFAULT_FORE "#000000"
|
shapers_to_string ()
|
||||||
#define DEFAULT_BACK "#FFFFFF"
|
|
||||||
#define FONT_SIZE_UPEM 0x7FFFFFFF
|
|
||||||
#define FONT_SIZE_NONE 0
|
|
||||||
|
|
||||||
struct view_options_t : option_group_t
|
|
||||||
{
|
{
|
||||||
view_options_t (option_parser_t *parser)
|
GString *shapers = g_string_new (nullptr);
|
||||||
{
|
const char **shaper_list = hb_shape_list_shapers ();
|
||||||
annotate = false;
|
|
||||||
fore = nullptr;
|
|
||||||
back = nullptr;
|
|
||||||
line_space = 0;
|
|
||||||
have_font_extents = false;
|
|
||||||
font_extents.ascent = font_extents.descent = font_extents.line_gap = 0;
|
|
||||||
margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN;
|
|
||||||
|
|
||||||
add_options (parser);
|
for (; *shaper_list; shaper_list++) {
|
||||||
}
|
g_string_append (shapers, *shaper_list);
|
||||||
~view_options_t () override
|
g_string_append_c (shapers, ',');
|
||||||
{
|
|
||||||
g_free (fore);
|
|
||||||
g_free (back);
|
|
||||||
}
|
}
|
||||||
|
g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
|
||||||
|
|
||||||
void add_options (option_parser_t *parser) override;
|
return g_string_free (shapers, false);
|
||||||
|
}
|
||||||
|
|
||||||
hb_bool_t annotate;
|
static G_GNUC_NORETURN gboolean
|
||||||
char *fore;
|
show_version (const char *name G_GNUC_UNUSED,
|
||||||
char *back;
|
const char *arg G_GNUC_UNUSED,
|
||||||
double line_space;
|
gpointer data G_GNUC_UNUSED,
|
||||||
bool have_font_extents;
|
GError **error G_GNUC_UNUSED)
|
||||||
struct font_extents_t {
|
|
||||||
double ascent, descent, line_gap;
|
|
||||||
} font_extents;
|
|
||||||
struct margin_t {
|
|
||||||
double t, r, b, l;
|
|
||||||
} margin;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct shape_options_t : option_group_t
|
|
||||||
{
|
{
|
||||||
shape_options_t (option_parser_t *parser)
|
g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
|
||||||
|
|
||||||
|
char *shapers = shapers_to_string ();
|
||||||
|
g_printf ("Available shapers: %s\n", shapers);
|
||||||
|
g_free (shapers);
|
||||||
|
if (strcmp (HB_VERSION_STRING, hb_version_string ()))
|
||||||
|
g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
option_parser_t::add_main_options ()
|
||||||
|
{
|
||||||
|
GOptionEntry entries[] =
|
||||||
{
|
{
|
||||||
direction = language = script = nullptr;
|
{"version", 0, G_OPTION_FLAG_NO_ARG,
|
||||||
bot = eot = preserve_default_ignorables = remove_default_ignorables = false;
|
G_OPTION_ARG_CALLBACK, (gpointer) &show_version, "Show version numbers", nullptr},
|
||||||
features = nullptr;
|
{nullptr}
|
||||||
num_features = 0;
|
};
|
||||||
shapers = nullptr;
|
g_option_context_add_main_entries (context, entries, nullptr);
|
||||||
utf8_clusters = false;
|
}
|
||||||
invisible_glyph = 0;
|
|
||||||
cluster_level = HB_BUFFER_CLUSTER_LEVEL_DEFAULT;
|
|
||||||
normalize_glyphs = false;
|
|
||||||
verify = false;
|
|
||||||
num_iterations = 1;
|
|
||||||
|
|
||||||
add_options (parser);
|
inline void
|
||||||
}
|
option_parser_t::parse (int *argc, char ***argv)
|
||||||
~shape_options_t () override
|
{
|
||||||
|
setlocale (LC_ALL, "");
|
||||||
|
|
||||||
|
GError *parse_error = nullptr;
|
||||||
|
if (!g_option_context_parse (context, argc, argv, &parse_error))
|
||||||
{
|
{
|
||||||
g_free (direction);
|
if (parse_error)
|
||||||
g_free (language);
|
|
||||||
g_free (script);
|
|
||||||
free (features);
|
|
||||||
g_strfreev (shapers);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_options (option_parser_t *parser) override;
|
|
||||||
|
|
||||||
void setup_buffer (hb_buffer_t *buffer)
|
|
||||||
{
|
{
|
||||||
hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
|
fail (true, "%s", parse_error->message);
|
||||||
hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
|
//g_error_free (parse_error);
|
||||||
hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
|
|
||||||
hb_buffer_set_flags (buffer, (hb_buffer_flags_t)
|
|
||||||
(HB_BUFFER_FLAG_DEFAULT |
|
|
||||||
(bot ? HB_BUFFER_FLAG_BOT : 0) |
|
|
||||||
(eot ? HB_BUFFER_FLAG_EOT : 0) |
|
|
||||||
(preserve_default_ignorables ? HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES : 0) |
|
|
||||||
(remove_default_ignorables ? HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES : 0) |
|
|
||||||
0));
|
|
||||||
hb_buffer_set_invisible_glyph (buffer, invisible_glyph);
|
|
||||||
hb_buffer_set_cluster_level (buffer, cluster_level);
|
|
||||||
hb_buffer_guess_segment_properties (buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void copy_buffer_properties (hb_buffer_t *dst, hb_buffer_t *src)
|
|
||||||
{
|
|
||||||
hb_segment_properties_t props;
|
|
||||||
hb_buffer_get_segment_properties (src, &props);
|
|
||||||
hb_buffer_set_segment_properties (dst, &props);
|
|
||||||
hb_buffer_set_flags (dst, hb_buffer_get_flags (src));
|
|
||||||
hb_buffer_set_cluster_level (dst, hb_buffer_get_cluster_level (src));
|
|
||||||
}
|
|
||||||
|
|
||||||
void populate_buffer (hb_buffer_t *buffer, const char *text, int text_len,
|
|
||||||
const char *text_before, const char *text_after)
|
|
||||||
{
|
|
||||||
hb_buffer_clear_contents (buffer);
|
|
||||||
if (text_before) {
|
|
||||||
unsigned int len = strlen (text_before);
|
|
||||||
hb_buffer_add_utf8 (buffer, text_before, len, len, 0);
|
|
||||||
}
|
|
||||||
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
|
|
||||||
if (text_after) {
|
|
||||||
hb_buffer_add_utf8 (buffer, text_after, -1, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!utf8_clusters) {
|
|
||||||
/* Reset cluster values to refer to Unicode character index
|
|
||||||
* instead of UTF-8 index. */
|
|
||||||
unsigned int num_glyphs = hb_buffer_get_length (buffer);
|
|
||||||
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr);
|
|
||||||
for (unsigned int i = 0; i < num_glyphs; i++)
|
|
||||||
{
|
|
||||||
info->cluster = i;
|
|
||||||
info++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_buffer (buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
hb_bool_t shape (hb_font_t *font, hb_buffer_t *buffer, const char **error=nullptr)
|
|
||||||
{
|
|
||||||
hb_buffer_t *text_buffer = nullptr;
|
|
||||||
if (verify)
|
|
||||||
{
|
|
||||||
text_buffer = hb_buffer_create ();
|
|
||||||
hb_buffer_append (text_buffer, buffer, 0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hb_shape_full (font, buffer, features, num_features, shapers))
|
|
||||||
{
|
|
||||||
if (error)
|
|
||||||
*error = "all shapers failed.";
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalize_glyphs)
|
|
||||||
hb_buffer_normalize_glyphs (buffer);
|
|
||||||
|
|
||||||
if (verify && !verify_buffer (buffer, text_buffer, font, error))
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
if (text_buffer)
|
|
||||||
hb_buffer_destroy (text_buffer);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
if (text_buffer)
|
|
||||||
hb_buffer_destroy (text_buffer);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool verify_buffer (hb_buffer_t *buffer,
|
|
||||||
hb_buffer_t *text_buffer,
|
|
||||||
hb_font_t *font,
|
|
||||||
const char **error=nullptr)
|
|
||||||
{
|
|
||||||
if (!verify_buffer_monotone (buffer, error))
|
|
||||||
return false;
|
|
||||||
if (!verify_buffer_safe_to_break (buffer, text_buffer, font, error))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool verify_buffer_monotone (hb_buffer_t *buffer, const char **error=nullptr)
|
|
||||||
{
|
|
||||||
/* Check that clusters are monotone. */
|
|
||||||
if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES ||
|
|
||||||
cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
|
|
||||||
{
|
|
||||||
bool is_forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
|
|
||||||
|
|
||||||
unsigned int num_glyphs;
|
|
||||||
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
|
|
||||||
|
|
||||||
for (unsigned int i = 1; i < num_glyphs; i++)
|
|
||||||
if (info[i-1].cluster != info[i].cluster &&
|
|
||||||
(info[i-1].cluster < info[i].cluster) != is_forward)
|
|
||||||
{
|
|
||||||
if (error)
|
|
||||||
*error = "clusters are not monotone.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool verify_buffer_safe_to_break (hb_buffer_t *buffer,
|
|
||||||
hb_buffer_t *text_buffer,
|
|
||||||
hb_font_t *font,
|
|
||||||
const char **error=nullptr)
|
|
||||||
{
|
|
||||||
if (cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES &&
|
|
||||||
cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
|
|
||||||
{
|
|
||||||
/* Cannot perform this check without monotone clusters.
|
|
||||||
* Then again, unsafe-to-break flag is much harder to use without
|
|
||||||
* monotone clusters. */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check that breaking up shaping at safe-to-break is indeed safe. */
|
|
||||||
|
|
||||||
hb_buffer_t *fragment = hb_buffer_create ();
|
|
||||||
hb_buffer_t *reconstruction = hb_buffer_create ();
|
|
||||||
copy_buffer_properties (reconstruction, buffer);
|
|
||||||
|
|
||||||
unsigned int num_glyphs;
|
|
||||||
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
|
|
||||||
|
|
||||||
unsigned int num_chars;
|
|
||||||
hb_glyph_info_t *text = hb_buffer_get_glyph_infos (text_buffer, &num_chars);
|
|
||||||
|
|
||||||
/* Chop text and shape fragments. */
|
|
||||||
bool forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
|
|
||||||
unsigned int start = 0;
|
|
||||||
unsigned int text_start = forward ? 0 : num_chars;
|
|
||||||
unsigned int text_end = text_start;
|
|
||||||
for (unsigned int end = 1; end < num_glyphs + 1; end++)
|
|
||||||
{
|
|
||||||
if (end < num_glyphs &&
|
|
||||||
(info[end].cluster == info[end-1].cluster ||
|
|
||||||
info[end-(forward?0:1)].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Shape segment corresponding to glyphs start..end. */
|
|
||||||
if (end == num_glyphs)
|
|
||||||
{
|
|
||||||
if (forward)
|
|
||||||
text_end = num_chars;
|
|
||||||
else
|
|
||||||
text_start = 0;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
fail (true, "Option parse error");
|
||||||
if (forward)
|
|
||||||
{
|
|
||||||
unsigned int cluster = info[end].cluster;
|
|
||||||
while (text_end < num_chars && text[text_end].cluster < cluster)
|
|
||||||
text_end++;
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
|
||||||
unsigned int cluster = info[end - 1].cluster;
|
|
||||||
while (text_start && text[text_start - 1].cluster >= cluster)
|
|
||||||
text_start--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert (text_start < text_end);
|
|
||||||
|
|
||||||
if (0)
|
|
||||||
printf("start %d end %d text start %d end %d\n", start, end, text_start, text_end);
|
|
||||||
|
|
||||||
hb_buffer_clear_contents (fragment);
|
|
||||||
copy_buffer_properties (fragment, buffer);
|
|
||||||
|
|
||||||
/* TODO: Add pre/post context text. */
|
|
||||||
hb_buffer_flags_t flags = hb_buffer_get_flags (fragment);
|
|
||||||
if (0 < text_start)
|
|
||||||
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_BOT);
|
|
||||||
if (text_end < num_chars)
|
|
||||||
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_EOT);
|
|
||||||
hb_buffer_set_flags (fragment, flags);
|
|
||||||
|
|
||||||
hb_buffer_append (fragment, text_buffer, text_start, text_end);
|
|
||||||
if (!hb_shape_full (font, fragment, features, num_features, shapers))
|
|
||||||
{
|
|
||||||
if (error)
|
|
||||||
*error = "all shapers failed while shaping fragment.";
|
|
||||||
hb_buffer_destroy (reconstruction);
|
|
||||||
hb_buffer_destroy (fragment);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
hb_buffer_append (reconstruction, fragment, 0, -1);
|
|
||||||
|
|
||||||
start = end;
|
|
||||||
if (forward)
|
|
||||||
text_start = text_end;
|
|
||||||
else
|
|
||||||
text_end = text_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ret = true;
|
|
||||||
hb_buffer_diff_flags_t diff = hb_buffer_diff (reconstruction, buffer, (hb_codepoint_t) -1, 0);
|
|
||||||
if (diff)
|
|
||||||
{
|
|
||||||
if (error)
|
|
||||||
*error = "Safe-to-break test failed.";
|
|
||||||
ret = false;
|
|
||||||
|
|
||||||
/* Return the reconstructed result instead so it can be inspected. */
|
|
||||||
hb_buffer_set_length (buffer, 0);
|
|
||||||
hb_buffer_append (buffer, reconstruction, 0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
hb_buffer_destroy (reconstruction);
|
|
||||||
hb_buffer_destroy (fragment);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void shape_closure (const char *text, int text_len,
|
|
||||||
hb_font_t *font, hb_buffer_t *buffer,
|
|
||||||
hb_set_t *glyphs)
|
|
||||||
{
|
|
||||||
hb_buffer_reset (buffer);
|
|
||||||
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
|
|
||||||
setup_buffer (buffer);
|
|
||||||
hb_ot_shape_glyphs_closure (font, buffer, features, num_features, glyphs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buffer properties */
|
|
||||||
char *direction;
|
|
||||||
char *language;
|
|
||||||
char *script;
|
|
||||||
|
|
||||||
/* Buffer flags */
|
|
||||||
hb_bool_t bot;
|
|
||||||
hb_bool_t eot;
|
|
||||||
hb_bool_t preserve_default_ignorables;
|
|
||||||
hb_bool_t remove_default_ignorables;
|
|
||||||
|
|
||||||
hb_feature_t *features;
|
|
||||||
unsigned int num_features;
|
|
||||||
char **shapers;
|
|
||||||
hb_bool_t utf8_clusters;
|
|
||||||
hb_codepoint_t invisible_glyph;
|
|
||||||
hb_buffer_cluster_level_t cluster_level;
|
|
||||||
hb_bool_t normalize_glyphs;
|
|
||||||
hb_bool_t verify;
|
|
||||||
unsigned int num_iterations;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct font_options_t : option_group_t
|
|
||||||
{
|
|
||||||
font_options_t (option_parser_t *parser,
|
|
||||||
int default_font_size_,
|
|
||||||
unsigned int subpixel_bits_)
|
|
||||||
{
|
|
||||||
variations = nullptr;
|
|
||||||
num_variations = 0;
|
|
||||||
default_font_size = default_font_size_;
|
|
||||||
x_ppem = 0;
|
|
||||||
y_ppem = 0;
|
|
||||||
ptem = 0.;
|
|
||||||
subpixel_bits = subpixel_bits_;
|
|
||||||
font_file = nullptr;
|
|
||||||
face_index = 0;
|
|
||||||
font_size_x = font_size_y = default_font_size;
|
|
||||||
font_funcs = nullptr;
|
|
||||||
ft_load_flags = 2;
|
|
||||||
|
|
||||||
blob = nullptr;
|
|
||||||
font = nullptr;
|
|
||||||
|
|
||||||
add_options (parser);
|
|
||||||
}
|
|
||||||
~font_options_t () override
|
|
||||||
{
|
|
||||||
g_free (font_file);
|
|
||||||
free (variations);
|
|
||||||
g_free (font_funcs);
|
|
||||||
hb_font_destroy (font);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_options (option_parser_t *parser) override;
|
|
||||||
|
|
||||||
hb_font_t *get_font () const;
|
|
||||||
|
|
||||||
char *font_file;
|
|
||||||
mutable hb_blob_t *blob;
|
|
||||||
unsigned face_index;
|
|
||||||
hb_variation_t *variations;
|
|
||||||
unsigned int num_variations;
|
|
||||||
int default_font_size;
|
|
||||||
int x_ppem;
|
|
||||||
int y_ppem;
|
|
||||||
double ptem;
|
|
||||||
unsigned int subpixel_bits;
|
|
||||||
mutable double font_size_x;
|
|
||||||
mutable double font_size_y;
|
|
||||||
char *font_funcs;
|
|
||||||
int ft_load_flags;
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable hb_font_t *font;
|
|
||||||
|
|
||||||
static struct cache_t
|
|
||||||
{
|
|
||||||
~cache_t ()
|
|
||||||
{
|
|
||||||
free ((void *) font_path);
|
|
||||||
hb_blob_destroy (blob);
|
|
||||||
hb_face_destroy (face);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *font_path = nullptr;
|
|
||||||
hb_blob_t *blob = nullptr;
|
|
||||||
unsigned face_index = (unsigned) -1;
|
|
||||||
hb_face_t *face = nullptr;
|
|
||||||
} cache;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct text_options_t : option_group_t
|
|
||||||
{
|
|
||||||
text_options_t (option_parser_t *parser)
|
|
||||||
{
|
|
||||||
text_before = nullptr;
|
|
||||||
text_after = nullptr;
|
|
||||||
|
|
||||||
text_len = -1;
|
|
||||||
text = nullptr;
|
|
||||||
text_file = nullptr;
|
|
||||||
|
|
||||||
fp = nullptr;
|
|
||||||
gs = nullptr;
|
|
||||||
line = nullptr;
|
|
||||||
line_len = UINT_MAX;
|
|
||||||
|
|
||||||
add_options (parser);
|
|
||||||
}
|
|
||||||
~text_options_t () override
|
|
||||||
{
|
|
||||||
g_free (text_before);
|
|
||||||
g_free (text_after);
|
|
||||||
g_free (text);
|
|
||||||
g_free (text_file);
|
|
||||||
if (gs)
|
|
||||||
g_string_free (gs, true);
|
|
||||||
if (fp && fp != stdin)
|
|
||||||
fclose (fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_options (option_parser_t *parser) override;
|
|
||||||
|
|
||||||
void post_parse (GError **error G_GNUC_UNUSED) override {
|
|
||||||
if (text && text_file)
|
|
||||||
g_set_error (error,
|
|
||||||
G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
|
||||||
"Only one of text and text-file can be set");
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *get_line (unsigned int *len, int eol = '\n');
|
|
||||||
|
|
||||||
char *text_before;
|
|
||||||
char *text_after;
|
|
||||||
|
|
||||||
int text_len;
|
|
||||||
char *text;
|
|
||||||
char *text_file;
|
|
||||||
|
|
||||||
private:
|
|
||||||
FILE *fp;
|
|
||||||
GString *gs;
|
|
||||||
char *line;
|
|
||||||
unsigned int line_len;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct output_options_t : option_group_t
|
|
||||||
{
|
|
||||||
output_options_t (option_parser_t *parser,
|
|
||||||
const char **supported_formats_ = nullptr)
|
|
||||||
{
|
|
||||||
output_file = nullptr;
|
|
||||||
output_format = nullptr;
|
|
||||||
supported_formats = supported_formats_;
|
|
||||||
explicit_output_format = false;
|
|
||||||
|
|
||||||
fp = nullptr;
|
|
||||||
|
|
||||||
add_options (parser);
|
|
||||||
}
|
|
||||||
~output_options_t () override
|
|
||||||
{
|
|
||||||
g_free (output_file);
|
|
||||||
g_free (output_format);
|
|
||||||
if (fp && fp != stdout)
|
|
||||||
fclose (fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_options (option_parser_t *parser) override;
|
|
||||||
|
|
||||||
void post_parse (GError **error G_GNUC_UNUSED) override
|
|
||||||
{
|
|
||||||
if (output_format)
|
|
||||||
explicit_output_format = true;
|
|
||||||
|
|
||||||
if (output_file && !output_format) {
|
|
||||||
output_format = strrchr (output_file, '.');
|
|
||||||
if (output_format)
|
|
||||||
{
|
|
||||||
output_format++; /* skip the dot */
|
|
||||||
output_format = g_strdup (output_format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output_file && 0 == strcmp (output_file, "-"))
|
|
||||||
output_file = nullptr; /* STDOUT */
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *get_file_handle ();
|
|
||||||
|
|
||||||
char *output_file;
|
|
||||||
char *output_format;
|
|
||||||
const char **supported_formats;
|
|
||||||
bool explicit_output_format;
|
|
||||||
|
|
||||||
mutable FILE *fp;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct format_options_t : option_group_t
|
|
||||||
{
|
|
||||||
format_options_t (option_parser_t *parser) {
|
|
||||||
show_glyph_names = true;
|
|
||||||
show_positions = true;
|
|
||||||
show_advances = true;
|
|
||||||
show_clusters = true;
|
|
||||||
show_text = false;
|
|
||||||
show_unicode = false;
|
|
||||||
show_line_num = false;
|
|
||||||
show_extents = false;
|
|
||||||
show_flags = false;
|
|
||||||
trace = false;
|
|
||||||
|
|
||||||
add_options (parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_options (option_parser_t *parser) override;
|
|
||||||
|
|
||||||
void serialize (hb_buffer_t *buffer,
|
|
||||||
hb_font_t *font,
|
|
||||||
hb_buffer_serialize_format_t format,
|
|
||||||
hb_buffer_serialize_flags_t flags,
|
|
||||||
GString *gs);
|
|
||||||
void serialize_line_no (unsigned int line_no,
|
|
||||||
GString *gs);
|
|
||||||
void serialize_buffer_of_text (hb_buffer_t *buffer,
|
|
||||||
unsigned int line_no,
|
|
||||||
const char *text,
|
|
||||||
unsigned int text_len,
|
|
||||||
hb_font_t *font,
|
|
||||||
GString *gs);
|
|
||||||
void serialize_message (unsigned int line_no,
|
|
||||||
const char *type,
|
|
||||||
const char *msg,
|
|
||||||
GString *gs);
|
|
||||||
void serialize_buffer_of_glyphs (hb_buffer_t *buffer,
|
|
||||||
unsigned int line_no,
|
|
||||||
const char *text,
|
|
||||||
unsigned int text_len,
|
|
||||||
hb_font_t *font,
|
|
||||||
hb_buffer_serialize_format_t output_format,
|
|
||||||
hb_buffer_serialize_flags_t format_flags,
|
|
||||||
GString *gs);
|
|
||||||
|
|
||||||
|
|
||||||
hb_bool_t show_glyph_names;
|
|
||||||
hb_bool_t show_positions;
|
|
||||||
hb_bool_t show_advances;
|
|
||||||
hb_bool_t show_clusters;
|
|
||||||
hb_bool_t show_text;
|
|
||||||
hb_bool_t show_unicode;
|
|
||||||
hb_bool_t show_line_num;
|
|
||||||
hb_bool_t show_extents;
|
|
||||||
hb_bool_t show_flags;
|
|
||||||
hb_bool_t trace;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct subset_options_t : option_group_t
|
|
||||||
{
|
|
||||||
subset_options_t (option_parser_t *parser)
|
|
||||||
{
|
|
||||||
input = hb_subset_input_create_or_fail ();
|
|
||||||
num_iterations = 1;
|
|
||||||
add_options (parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
~subset_options_t () override
|
|
||||||
{
|
|
||||||
hb_subset_input_destroy (input);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_options (option_parser_t *parser) override;
|
|
||||||
|
|
||||||
hb_bool_t* bool_for(hb_subset_flags_t flag)
|
|
||||||
{
|
|
||||||
for (unsigned i = 0; i < sizeof(int) * 8; i++)
|
|
||||||
{
|
|
||||||
if (1u << i == flag)
|
|
||||||
return &flags[i];
|
|
||||||
}
|
|
||||||
return &flags[sizeof(int) * 8 - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned num_iterations;
|
|
||||||
|
|
||||||
hb_subset_input_t * get_input ()
|
|
||||||
{
|
|
||||||
hb_subset_flags_t flags_set = HB_SUBSET_FLAGS_DEFAULT;
|
|
||||||
for (unsigned i = 0; i < sizeof(int) * 8; i++)
|
|
||||||
{
|
|
||||||
if (flags[i])
|
|
||||||
flags_set = (hb_subset_flags_t) (flags_set | (1u << i));
|
|
||||||
}
|
|
||||||
hb_subset_input_set_flags (input, flags_set);
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
hb_bool_t flags[sizeof(int) * 8] = {0};
|
|
||||||
|
|
||||||
hb_subset_input_t *input;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* fallback implementation for scalbn()/scalbnf() for pre-2013 MSVC */
|
/* fallback implementation for scalbn()/scalbnf() for pre-2013 MSVC */
|
||||||
#if defined (_MSC_VER) && (_MSC_VER < 1800)
|
#if defined (_MSC_VER) && (_MSC_VER < 1800)
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Google, Inc.
|
||||||
|
*
|
||||||
|
* This is part of HarfBuzz, a text shaping library.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, without written agreement and without
|
||||||
|
* license or royalty fees, to use, copy, modify, and distribute this
|
||||||
|
* software and its documentation for any purpose, provided that the
|
||||||
|
* above copyright notice and the following two paragraphs appear in
|
||||||
|
* all copies of this software.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||||
|
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||||
|
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||||
|
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||||
|
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||||
|
*
|
||||||
|
* Google Author(s): Behdad Esfahbod
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OUTPUT_OPTIONS_HH
|
||||||
|
#define OUTPUT_OPTIONS_HH
|
||||||
|
|
||||||
|
#include "options.hh"
|
||||||
|
|
||||||
|
struct output_options_t
|
||||||
|
{
|
||||||
|
~output_options_t ()
|
||||||
|
{
|
||||||
|
g_free (output_file);
|
||||||
|
g_free (output_format);
|
||||||
|
if (fp && fp != stdout)
|
||||||
|
fclose (fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_options (option_parser_t *parser,
|
||||||
|
const char **supported_formats = nullptr);
|
||||||
|
|
||||||
|
void post_parse (GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
if (output_format)
|
||||||
|
explicit_output_format = true;
|
||||||
|
|
||||||
|
if (output_file && !output_format) {
|
||||||
|
output_format = strrchr (output_file, '.');
|
||||||
|
if (output_format)
|
||||||
|
{
|
||||||
|
output_format++; /* skip the dot */
|
||||||
|
output_format = g_strdup (output_format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output_file && 0 == strcmp (output_file, "-"))
|
||||||
|
output_file = nullptr; /* STDOUT */
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *get_file_handle ();
|
||||||
|
|
||||||
|
char *output_file = nullptr;
|
||||||
|
char *output_format = nullptr;
|
||||||
|
bool explicit_output_format = false;
|
||||||
|
|
||||||
|
mutable FILE *fp = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
FILE *
|
||||||
|
output_options_t::get_file_handle ()
|
||||||
|
{
|
||||||
|
if (fp)
|
||||||
|
return fp;
|
||||||
|
|
||||||
|
if (output_file)
|
||||||
|
fp = fopen (output_file, "wb");
|
||||||
|
else {
|
||||||
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||||
|
setmode (fileno (stdout), O_BINARY);
|
||||||
|
#endif
|
||||||
|
fp = stdout;
|
||||||
|
}
|
||||||
|
if (!fp)
|
||||||
|
fail (false, "Cannot open output file `%s': %s",
|
||||||
|
g_filename_display_name (output_file), strerror (errno));
|
||||||
|
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
output_options_t::add_options (option_parser_t *parser,
|
||||||
|
const char **supported_formats)
|
||||||
|
{
|
||||||
|
const char *text = nullptr;
|
||||||
|
|
||||||
|
if (supported_formats)
|
||||||
|
{
|
||||||
|
char *items = g_strjoinv ("/", const_cast<char **> (supported_formats));
|
||||||
|
text = g_strdup_printf ("Set output format\n\n Supported output formats are: %s", items);
|
||||||
|
g_free (items);
|
||||||
|
parser->free_later ((char *) text);
|
||||||
|
}
|
||||||
|
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{"output-file", 'o', 0, G_OPTION_ARG_STRING, &this->output_file, "Set output file-name (default: stdout)","filename"},
|
||||||
|
{"output-format", 'O', supported_formats ? 0 : G_OPTION_FLAG_HIDDEN,
|
||||||
|
G_OPTION_ARG_STRING, &this->output_format, text, "format"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries,
|
||||||
|
"output",
|
||||||
|
"Output destination & format options:",
|
||||||
|
"Options for the destination & form of the output",
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -27,26 +27,24 @@
|
||||||
#ifndef HB_SHAPE_CONSUMER_HH
|
#ifndef HB_SHAPE_CONSUMER_HH
|
||||||
#define HB_SHAPE_CONSUMER_HH
|
#define HB_SHAPE_CONSUMER_HH
|
||||||
|
|
||||||
#include "hb.hh"
|
#include "font-options.hh"
|
||||||
#include "options.hh"
|
#include "shape-options.hh"
|
||||||
|
|
||||||
|
|
||||||
template <typename output_t>
|
template <typename output_t>
|
||||||
struct shape_consumer_t
|
struct shape_consumer_t : shape_options_t
|
||||||
{
|
{
|
||||||
shape_consumer_t (option_parser_t *parser)
|
void add_options (option_parser_t *parser)
|
||||||
: failed (false),
|
{
|
||||||
shaper (parser),
|
shape_options_t::add_options (parser);
|
||||||
output (parser),
|
output.add_options (parser);
|
||||||
font (nullptr),
|
}
|
||||||
buffer (nullptr) {}
|
|
||||||
|
|
||||||
void init (hb_buffer_t *buffer_,
|
void init (const font_options_t *font_opts)
|
||||||
const font_options_t *font_opts)
|
|
||||||
{
|
{
|
||||||
font = hb_font_reference (font_opts->get_font ());
|
font = hb_font_reference (font_opts->get_font ());
|
||||||
failed = false;
|
failed = false;
|
||||||
buffer = hb_buffer_reference (buffer_);
|
buffer = hb_buffer_create ();
|
||||||
|
|
||||||
output.init (buffer, font_opts);
|
output.init (buffer, font_opts);
|
||||||
}
|
}
|
||||||
|
@ -57,14 +55,14 @@ struct shape_consumer_t
|
||||||
{
|
{
|
||||||
output.new_line ();
|
output.new_line ();
|
||||||
|
|
||||||
for (unsigned int n = shaper.num_iterations; n; n--)
|
for (unsigned int n = num_iterations; n; n--)
|
||||||
{
|
{
|
||||||
const char *error = nullptr;
|
const char *error = nullptr;
|
||||||
|
|
||||||
shaper.populate_buffer (buffer, text, text_len, text_before, text_after);
|
populate_buffer (buffer, text, text_len, text_before, text_after);
|
||||||
if (n == 1)
|
if (n == 1)
|
||||||
output.consume_text (buffer, text, text_len, shaper.utf8_clusters);
|
output.consume_text (buffer, text, text_len, utf8_clusters);
|
||||||
if (!shaper.shape (font, buffer, &error))
|
if (!shape (font, buffer, &error))
|
||||||
{
|
{
|
||||||
failed = true;
|
failed = true;
|
||||||
output.error (error);
|
output.error (error);
|
||||||
|
@ -75,7 +73,7 @@ struct shape_consumer_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output.consume_glyphs (buffer, text, text_len, shaper.utf8_clusters);
|
output.consume_glyphs (buffer, text, text_len, utf8_clusters);
|
||||||
}
|
}
|
||||||
void finish (const font_options_t *font_opts)
|
void finish (const font_options_t *font_opts)
|
||||||
{
|
{
|
||||||
|
@ -87,14 +85,13 @@ struct shape_consumer_t
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool failed;
|
bool failed = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
shape_options_t shaper;
|
|
||||||
output_t output;
|
output_t output;
|
||||||
|
|
||||||
hb_font_t *font;
|
hb_font_t *font = nullptr;
|
||||||
hb_buffer_t *buffer;
|
hb_buffer_t *buffer = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Google, Inc.
|
||||||
|
*
|
||||||
|
* This is part of HarfBuzz, a text shaping library.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, without written agreement and without
|
||||||
|
* license or royalty fees, to use, copy, modify, and distribute this
|
||||||
|
* software and its documentation for any purpose, provided that the
|
||||||
|
* above copyright notice and the following two paragraphs appear in
|
||||||
|
* all copies of this software.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||||
|
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||||
|
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||||
|
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||||
|
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||||
|
*
|
||||||
|
* Google Author(s): Behdad Esfahbod
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SHAPE_FORMAT_OPTIONS_HH
|
||||||
|
#define SHAPE_FORMAT_OPTIONS_HH
|
||||||
|
|
||||||
|
#include "options.hh"
|
||||||
|
|
||||||
|
|
||||||
|
struct shape_format_options_t
|
||||||
|
{
|
||||||
|
void add_options (option_parser_t *parser);
|
||||||
|
|
||||||
|
void serialize (hb_buffer_t *buffer,
|
||||||
|
hb_font_t *font,
|
||||||
|
hb_buffer_serialize_format_t format,
|
||||||
|
hb_buffer_serialize_flags_t flags,
|
||||||
|
GString *gs);
|
||||||
|
void serialize_line_no (unsigned int line_no,
|
||||||
|
GString *gs);
|
||||||
|
void serialize_buffer_of_text (hb_buffer_t *buffer,
|
||||||
|
unsigned int line_no,
|
||||||
|
const char *text,
|
||||||
|
unsigned int text_len,
|
||||||
|
hb_font_t *font,
|
||||||
|
GString *gs);
|
||||||
|
void serialize_message (unsigned int line_no,
|
||||||
|
const char *type,
|
||||||
|
const char *msg,
|
||||||
|
GString *gs);
|
||||||
|
void serialize_buffer_of_glyphs (hb_buffer_t *buffer,
|
||||||
|
unsigned int line_no,
|
||||||
|
const char *text,
|
||||||
|
unsigned int text_len,
|
||||||
|
hb_font_t *font,
|
||||||
|
hb_buffer_serialize_format_t output_format,
|
||||||
|
hb_buffer_serialize_flags_t format_flags,
|
||||||
|
GString *gs);
|
||||||
|
|
||||||
|
|
||||||
|
hb_bool_t show_glyph_names = true;
|
||||||
|
hb_bool_t show_positions = true;
|
||||||
|
hb_bool_t show_advances = true;
|
||||||
|
hb_bool_t show_clusters = true;
|
||||||
|
hb_bool_t show_text = false;
|
||||||
|
hb_bool_t show_unicode = false;
|
||||||
|
hb_bool_t show_line_num = false;
|
||||||
|
hb_bool_t show_extents = false;
|
||||||
|
hb_bool_t show_flags = false;
|
||||||
|
hb_bool_t trace = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_verbose (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg G_GNUC_UNUSED,
|
||||||
|
gpointer data G_GNUC_UNUSED,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
shape_format_options_t *format_opts = (shape_format_options_t *) data;
|
||||||
|
format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_ned (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg G_GNUC_UNUSED,
|
||||||
|
gpointer data G_GNUC_UNUSED,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
shape_format_options_t *format_opts = (shape_format_options_t *) data;
|
||||||
|
format_opts->show_clusters = format_opts->show_advances = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
shape_format_options_t::serialize (hb_buffer_t *buffer,
|
||||||
|
hb_font_t *font,
|
||||||
|
hb_buffer_serialize_format_t output_format,
|
||||||
|
hb_buffer_serialize_flags_t flags,
|
||||||
|
GString *gs)
|
||||||
|
{
|
||||||
|
unsigned int num_glyphs = hb_buffer_get_length (buffer);
|
||||||
|
unsigned int start = 0;
|
||||||
|
|
||||||
|
while (start < num_glyphs)
|
||||||
|
{
|
||||||
|
char buf[32768];
|
||||||
|
unsigned int consumed;
|
||||||
|
start += hb_buffer_serialize (buffer, start, num_glyphs,
|
||||||
|
buf, sizeof (buf), &consumed,
|
||||||
|
font, output_format, flags);
|
||||||
|
if (!consumed)
|
||||||
|
break;
|
||||||
|
g_string_append (gs, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
shape_format_options_t::serialize_line_no (unsigned int line_no,
|
||||||
|
GString *gs)
|
||||||
|
{
|
||||||
|
if (show_line_num)
|
||||||
|
g_string_append_printf (gs, "%d: ", line_no);
|
||||||
|
}
|
||||||
|
inline void
|
||||||
|
shape_format_options_t::serialize_buffer_of_text (hb_buffer_t *buffer,
|
||||||
|
unsigned int line_no,
|
||||||
|
const char *text,
|
||||||
|
unsigned int text_len,
|
||||||
|
hb_font_t *font,
|
||||||
|
GString *gs)
|
||||||
|
{
|
||||||
|
if (show_text)
|
||||||
|
{
|
||||||
|
serialize_line_no (line_no, gs);
|
||||||
|
g_string_append_c (gs, '(');
|
||||||
|
g_string_append_len (gs, text, text_len);
|
||||||
|
g_string_append_c (gs, ')');
|
||||||
|
g_string_append_c (gs, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_unicode)
|
||||||
|
{
|
||||||
|
serialize_line_no (line_no, gs);
|
||||||
|
serialize (buffer, font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT, gs);
|
||||||
|
g_string_append_c (gs, '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void
|
||||||
|
shape_format_options_t::serialize_message (unsigned int line_no,
|
||||||
|
const char *type,
|
||||||
|
const char *msg,
|
||||||
|
GString *gs)
|
||||||
|
{
|
||||||
|
serialize_line_no (line_no, gs);
|
||||||
|
g_string_append_printf (gs, "%s: %s", type, msg);
|
||||||
|
g_string_append_c (gs, '\n');
|
||||||
|
}
|
||||||
|
inline void
|
||||||
|
shape_format_options_t::serialize_buffer_of_glyphs (hb_buffer_t *buffer,
|
||||||
|
unsigned int line_no,
|
||||||
|
const char *text,
|
||||||
|
unsigned int text_len,
|
||||||
|
hb_font_t *font,
|
||||||
|
hb_buffer_serialize_format_t output_format,
|
||||||
|
hb_buffer_serialize_flags_t format_flags,
|
||||||
|
GString *gs)
|
||||||
|
{
|
||||||
|
serialize_line_no (line_no, gs);
|
||||||
|
serialize (buffer, font, output_format, format_flags, gs);
|
||||||
|
g_string_append_c (gs, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
shape_format_options_t::add_options (option_parser_t *parser)
|
||||||
|
{
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{"show-text", 0, 0, G_OPTION_ARG_NONE, &this->show_text, "Prefix each line of output with its corresponding input text", nullptr},
|
||||||
|
{"show-unicode", 0, 0, G_OPTION_ARG_NONE, &this->show_unicode, "Prefix each line of output with its corresponding input codepoint(s)", nullptr},
|
||||||
|
{"show-line-num", 0, 0, G_OPTION_ARG_NONE, &this->show_line_num, "Prefix each line of output with its corresponding input line number", nullptr},
|
||||||
|
{"verbose", 'v', G_OPTION_FLAG_NO_ARG,
|
||||||
|
G_OPTION_ARG_CALLBACK, (gpointer) &parse_verbose, "Prefix each line of output with all of the above", nullptr},
|
||||||
|
{"no-glyph-names", 0, G_OPTION_FLAG_REVERSE,
|
||||||
|
G_OPTION_ARG_NONE, &this->show_glyph_names, "Output glyph indices instead of names", nullptr},
|
||||||
|
{"no-positions", 0, G_OPTION_FLAG_REVERSE,
|
||||||
|
G_OPTION_ARG_NONE, &this->show_positions, "Do not output glyph positions", nullptr},
|
||||||
|
{"no-advances", 0, G_OPTION_FLAG_REVERSE,
|
||||||
|
G_OPTION_ARG_NONE, &this->show_advances, "Do not output glyph advances", nullptr},
|
||||||
|
{"no-clusters", 0, G_OPTION_FLAG_REVERSE,
|
||||||
|
G_OPTION_ARG_NONE, &this->show_clusters, "Do not output cluster indices", nullptr},
|
||||||
|
{"show-extents", 0, 0, G_OPTION_ARG_NONE, &this->show_extents, "Output glyph extents", nullptr},
|
||||||
|
{"show-flags", 0, 0, G_OPTION_ARG_NONE, &this->show_flags, "Output glyph flags", nullptr},
|
||||||
|
{"ned", 'v', G_OPTION_FLAG_NO_ARG,
|
||||||
|
G_OPTION_ARG_CALLBACK, (gpointer) &parse_ned, "No Extra Data; Do not output clusters or advances", nullptr},
|
||||||
|
{"trace", 'V', 0, G_OPTION_ARG_NONE, &this->trace, "Output interim shaping results", nullptr},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries,
|
||||||
|
"output-syntax",
|
||||||
|
"Output syntax:\n"
|
||||||
|
" text: [<glyph name or index>=<glyph cluster index within input>@<horizontal displacement>,<vertical displacement>+<horizontal advance>,<vertical advance>|...]\n"
|
||||||
|
" json: [{\"g\": <glyph name or index>, \"ax\": <horizontal advance>, \"ay\": <vertical advance>, \"dx\": <horizontal displacement>, \"dy\": <vertical displacement>, \"cl\": <glyph cluster index within input>}, ...]\n"
|
||||||
|
"\nOutput syntax options:",
|
||||||
|
"Options for the syntax of the output",
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,490 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Google, Inc.
|
||||||
|
*
|
||||||
|
* This is part of HarfBuzz, a text shaping library.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, without written agreement and without
|
||||||
|
* license or royalty fees, to use, copy, modify, and distribute this
|
||||||
|
* software and its documentation for any purpose, provided that the
|
||||||
|
* above copyright notice and the following two paragraphs appear in
|
||||||
|
* all copies of this software.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||||
|
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||||
|
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||||
|
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||||
|
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||||
|
*
|
||||||
|
* Google Author(s): Behdad Esfahbod
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SHAPE_OPTIONS_HH
|
||||||
|
#define SHAPE_OPTIONS_HH
|
||||||
|
|
||||||
|
#include "options.hh"
|
||||||
|
|
||||||
|
struct shape_options_t
|
||||||
|
{
|
||||||
|
~shape_options_t ()
|
||||||
|
{
|
||||||
|
g_free (direction);
|
||||||
|
g_free (language);
|
||||||
|
g_free (script);
|
||||||
|
free (features);
|
||||||
|
g_strfreev (shapers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_options (option_parser_t *parser);
|
||||||
|
|
||||||
|
void setup_buffer (hb_buffer_t *buffer)
|
||||||
|
{
|
||||||
|
hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
|
||||||
|
hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
|
||||||
|
hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
|
||||||
|
hb_buffer_set_flags (buffer, (hb_buffer_flags_t)
|
||||||
|
(HB_BUFFER_FLAG_DEFAULT |
|
||||||
|
(bot ? HB_BUFFER_FLAG_BOT : 0) |
|
||||||
|
(eot ? HB_BUFFER_FLAG_EOT : 0) |
|
||||||
|
(preserve_default_ignorables ? HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES : 0) |
|
||||||
|
(remove_default_ignorables ? HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES : 0) |
|
||||||
|
0));
|
||||||
|
hb_buffer_set_invisible_glyph (buffer, invisible_glyph);
|
||||||
|
hb_buffer_set_cluster_level (buffer, cluster_level);
|
||||||
|
hb_buffer_guess_segment_properties (buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy_buffer_properties (hb_buffer_t *dst, hb_buffer_t *src)
|
||||||
|
{
|
||||||
|
hb_segment_properties_t props;
|
||||||
|
hb_buffer_get_segment_properties (src, &props);
|
||||||
|
hb_buffer_set_segment_properties (dst, &props);
|
||||||
|
hb_buffer_set_flags (dst, hb_buffer_get_flags (src));
|
||||||
|
hb_buffer_set_cluster_level (dst, hb_buffer_get_cluster_level (src));
|
||||||
|
}
|
||||||
|
|
||||||
|
void populate_buffer (hb_buffer_t *buffer, const char *text, int text_len,
|
||||||
|
const char *text_before, const char *text_after)
|
||||||
|
{
|
||||||
|
hb_buffer_clear_contents (buffer);
|
||||||
|
if (text_before) {
|
||||||
|
unsigned int len = strlen (text_before);
|
||||||
|
hb_buffer_add_utf8 (buffer, text_before, len, len, 0);
|
||||||
|
}
|
||||||
|
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
|
||||||
|
if (text_after) {
|
||||||
|
hb_buffer_add_utf8 (buffer, text_after, -1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!utf8_clusters) {
|
||||||
|
/* Reset cluster values to refer to Unicode character index
|
||||||
|
* instead of UTF-8 index. */
|
||||||
|
unsigned int num_glyphs = hb_buffer_get_length (buffer);
|
||||||
|
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr);
|
||||||
|
for (unsigned int i = 0; i < num_glyphs; i++)
|
||||||
|
{
|
||||||
|
info->cluster = i;
|
||||||
|
info++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_buffer (buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
hb_bool_t shape (hb_font_t *font, hb_buffer_t *buffer, const char **error=nullptr)
|
||||||
|
{
|
||||||
|
hb_buffer_t *text_buffer = nullptr;
|
||||||
|
if (verify)
|
||||||
|
{
|
||||||
|
text_buffer = hb_buffer_create ();
|
||||||
|
hb_buffer_append (text_buffer, buffer, 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hb_shape_full (font, buffer, features, num_features, shapers))
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
*error = "all shapers failed.";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalize_glyphs)
|
||||||
|
hb_buffer_normalize_glyphs (buffer);
|
||||||
|
|
||||||
|
if (verify && !verify_buffer (buffer, text_buffer, font, error))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (text_buffer)
|
||||||
|
hb_buffer_destroy (text_buffer);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (text_buffer)
|
||||||
|
hb_buffer_destroy (text_buffer);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verify_buffer (hb_buffer_t *buffer,
|
||||||
|
hb_buffer_t *text_buffer,
|
||||||
|
hb_font_t *font,
|
||||||
|
const char **error=nullptr)
|
||||||
|
{
|
||||||
|
if (!verify_buffer_monotone (buffer, error))
|
||||||
|
return false;
|
||||||
|
if (!verify_buffer_safe_to_break (buffer, text_buffer, font, error))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verify_buffer_monotone (hb_buffer_t *buffer, const char **error=nullptr)
|
||||||
|
{
|
||||||
|
/* Check that clusters are monotone. */
|
||||||
|
if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES ||
|
||||||
|
cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
|
||||||
|
{
|
||||||
|
bool is_forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
|
||||||
|
|
||||||
|
unsigned int num_glyphs;
|
||||||
|
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
|
||||||
|
|
||||||
|
for (unsigned int i = 1; i < num_glyphs; i++)
|
||||||
|
if (info[i-1].cluster != info[i].cluster &&
|
||||||
|
(info[i-1].cluster < info[i].cluster) != is_forward)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
*error = "clusters are not monotone.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verify_buffer_safe_to_break (hb_buffer_t *buffer,
|
||||||
|
hb_buffer_t *text_buffer,
|
||||||
|
hb_font_t *font,
|
||||||
|
const char **error=nullptr)
|
||||||
|
{
|
||||||
|
if (cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES &&
|
||||||
|
cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
|
||||||
|
{
|
||||||
|
/* Cannot perform this check without monotone clusters.
|
||||||
|
* Then again, unsafe-to-break flag is much harder to use without
|
||||||
|
* monotone clusters. */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that breaking up shaping at safe-to-break is indeed safe. */
|
||||||
|
|
||||||
|
hb_buffer_t *fragment = hb_buffer_create ();
|
||||||
|
hb_buffer_t *reconstruction = hb_buffer_create ();
|
||||||
|
copy_buffer_properties (reconstruction, buffer);
|
||||||
|
|
||||||
|
unsigned int num_glyphs;
|
||||||
|
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
|
||||||
|
|
||||||
|
unsigned int num_chars;
|
||||||
|
hb_glyph_info_t *text = hb_buffer_get_glyph_infos (text_buffer, &num_chars);
|
||||||
|
|
||||||
|
/* Chop text and shape fragments. */
|
||||||
|
bool forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
|
||||||
|
unsigned int start = 0;
|
||||||
|
unsigned int text_start = forward ? 0 : num_chars;
|
||||||
|
unsigned int text_end = text_start;
|
||||||
|
for (unsigned int end = 1; end < num_glyphs + 1; end++)
|
||||||
|
{
|
||||||
|
if (end < num_glyphs &&
|
||||||
|
(info[end].cluster == info[end-1].cluster ||
|
||||||
|
info[end-(forward?0:1)].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Shape segment corresponding to glyphs start..end. */
|
||||||
|
if (end == num_glyphs)
|
||||||
|
{
|
||||||
|
if (forward)
|
||||||
|
text_end = num_chars;
|
||||||
|
else
|
||||||
|
text_start = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (forward)
|
||||||
|
{
|
||||||
|
unsigned int cluster = info[end].cluster;
|
||||||
|
while (text_end < num_chars && text[text_end].cluster < cluster)
|
||||||
|
text_end++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unsigned int cluster = info[end - 1].cluster;
|
||||||
|
while (text_start && text[text_start - 1].cluster >= cluster)
|
||||||
|
text_start--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert (text_start < text_end);
|
||||||
|
|
||||||
|
if (0)
|
||||||
|
printf("start %d end %d text start %d end %d\n", start, end, text_start, text_end);
|
||||||
|
|
||||||
|
hb_buffer_clear_contents (fragment);
|
||||||
|
copy_buffer_properties (fragment, buffer);
|
||||||
|
|
||||||
|
/* TODO: Add pre/post context text. */
|
||||||
|
hb_buffer_flags_t flags = hb_buffer_get_flags (fragment);
|
||||||
|
if (0 < text_start)
|
||||||
|
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_BOT);
|
||||||
|
if (text_end < num_chars)
|
||||||
|
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_EOT);
|
||||||
|
hb_buffer_set_flags (fragment, flags);
|
||||||
|
|
||||||
|
hb_buffer_append (fragment, text_buffer, text_start, text_end);
|
||||||
|
if (!hb_shape_full (font, fragment, features, num_features, shapers))
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
*error = "all shapers failed while shaping fragment.";
|
||||||
|
hb_buffer_destroy (reconstruction);
|
||||||
|
hb_buffer_destroy (fragment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hb_buffer_append (reconstruction, fragment, 0, -1);
|
||||||
|
|
||||||
|
start = end;
|
||||||
|
if (forward)
|
||||||
|
text_start = text_end;
|
||||||
|
else
|
||||||
|
text_end = text_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = true;
|
||||||
|
hb_buffer_diff_flags_t diff = hb_buffer_diff (reconstruction, buffer, (hb_codepoint_t) -1, 0);
|
||||||
|
if (diff)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
*error = "Safe-to-break test failed.";
|
||||||
|
ret = false;
|
||||||
|
|
||||||
|
/* Return the reconstructed result instead so it can be inspected. */
|
||||||
|
hb_buffer_set_length (buffer, 0);
|
||||||
|
hb_buffer_append (buffer, reconstruction, 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
hb_buffer_destroy (reconstruction);
|
||||||
|
hb_buffer_destroy (fragment);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shape_closure (const char *text, int text_len,
|
||||||
|
hb_font_t *font, hb_buffer_t *buffer,
|
||||||
|
hb_set_t *glyphs)
|
||||||
|
{
|
||||||
|
hb_buffer_reset (buffer);
|
||||||
|
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
|
||||||
|
setup_buffer (buffer);
|
||||||
|
hb_ot_shape_glyphs_closure (font, buffer, features, num_features, glyphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buffer properties */
|
||||||
|
char *direction = nullptr;
|
||||||
|
char *language = nullptr;
|
||||||
|
char *script = nullptr;
|
||||||
|
|
||||||
|
/* Buffer flags */
|
||||||
|
hb_bool_t bot = false;
|
||||||
|
hb_bool_t eot = false;
|
||||||
|
hb_bool_t preserve_default_ignorables = false;
|
||||||
|
hb_bool_t remove_default_ignorables = false;
|
||||||
|
|
||||||
|
hb_feature_t *features = nullptr;
|
||||||
|
unsigned int num_features = 0;
|
||||||
|
char **shapers = nullptr;
|
||||||
|
hb_bool_t utf8_clusters = false;
|
||||||
|
hb_codepoint_t invisible_glyph = 0;
|
||||||
|
hb_buffer_cluster_level_t cluster_level = HB_BUFFER_CLUSTER_LEVEL_DEFAULT;
|
||||||
|
hb_bool_t normalize_glyphs = false;
|
||||||
|
hb_bool_t verify = false;
|
||||||
|
unsigned int num_iterations = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_shapers (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
shape_options_t *shape_opts = (shape_options_t *) data;
|
||||||
|
char **shapers = g_strsplit (arg, ",", 0);
|
||||||
|
|
||||||
|
for (char **shaper = shapers; *shaper; shaper++) {
|
||||||
|
bool found = false;
|
||||||
|
for (const char **hb_shaper = hb_shape_list_shapers (); *hb_shaper; hb_shaper++) {
|
||||||
|
if (strcmp (*shaper, *hb_shaper) == 0) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"Unknown or unsupported shaper: %s", *shaper);
|
||||||
|
g_strfreev (shapers);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strfreev (shape_opts->shapers);
|
||||||
|
shape_opts->shapers = shapers;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static G_GNUC_NORETURN gboolean
|
||||||
|
list_shapers (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg G_GNUC_UNUSED,
|
||||||
|
gpointer data G_GNUC_UNUSED,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++)
|
||||||
|
g_printf ("%s\n", *shaper);
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_features (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
shape_options_t *shape_opts = (shape_options_t *) data;
|
||||||
|
char *s = (char *) arg;
|
||||||
|
size_t l = strlen (s);
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
shape_opts->num_features = 0;
|
||||||
|
g_free (shape_opts->features);
|
||||||
|
shape_opts->features = nullptr;
|
||||||
|
|
||||||
|
/* if the string is quoted, strip the quotes */
|
||||||
|
if (s[0] == s[l - 1] && (s[0] == '\"' || s[0] == '\''))
|
||||||
|
{
|
||||||
|
s[l - 1] = '\0';
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*s)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* count the features first, so we can allocate memory */
|
||||||
|
p = s;
|
||||||
|
do {
|
||||||
|
shape_opts->num_features++;
|
||||||
|
p = strchr (p, ',');
|
||||||
|
if (p)
|
||||||
|
p++;
|
||||||
|
} while (p);
|
||||||
|
|
||||||
|
shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
|
||||||
|
if (!shape_opts->features)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* now do the actual parsing */
|
||||||
|
p = s;
|
||||||
|
shape_opts->num_features = 0;
|
||||||
|
while (p && *p) {
|
||||||
|
char *end = strchr (p, ',');
|
||||||
|
if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features]))
|
||||||
|
shape_opts->num_features++;
|
||||||
|
p = end ? end + 1 : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
shape_options_t::add_options (option_parser_t *parser)
|
||||||
|
{
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{"list-shapers", 0, G_OPTION_FLAG_NO_ARG,
|
||||||
|
G_OPTION_ARG_CALLBACK, (gpointer) &list_shapers, "List available shapers and quit", nullptr},
|
||||||
|
{"shaper", 0, G_OPTION_FLAG_HIDDEN,
|
||||||
|
G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Hidden duplicate of --shapers", nullptr},
|
||||||
|
{"shapers", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Set comma-separated list of shapers to try","list"},
|
||||||
|
{"direction", 0, 0, G_OPTION_ARG_STRING, &this->direction, "Set text direction (default: auto)", "ltr/rtl/ttb/btt"},
|
||||||
|
{"language", 0, 0, G_OPTION_ARG_STRING, &this->language, "Set text language (default: $LANG)", "langstr"},
|
||||||
|
{"script", 0, 0, G_OPTION_ARG_STRING, &this->script, "Set text script (default: auto)", "ISO-15924 tag"},
|
||||||
|
{"bot", 0, 0, G_OPTION_ARG_NONE, &this->bot, "Treat text as beginning-of-paragraph", nullptr},
|
||||||
|
{"eot", 0, 0, G_OPTION_ARG_NONE, &this->eot, "Treat text as end-of-paragraph", nullptr},
|
||||||
|
{"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->preserve_default_ignorables, "Preserve Default-Ignorable characters", nullptr},
|
||||||
|
{"remove-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->remove_default_ignorables, "Remove Default-Ignorable characters", nullptr},
|
||||||
|
{"invisible-glyph", 0, 0, G_OPTION_ARG_INT, &this->invisible_glyph, "Glyph value to replace Default-Ignorables with", nullptr},
|
||||||
|
{"utf8-clusters", 0, 0, G_OPTION_ARG_NONE, &this->utf8_clusters, "Use UTF8 byte indices, not char indices", nullptr},
|
||||||
|
{"cluster-level", 0, 0, G_OPTION_ARG_INT, &this->cluster_level, "Cluster merging level (default: 0)", "0/1/2"},
|
||||||
|
{"normalize-glyphs",0, 0, G_OPTION_ARG_NONE, &this->normalize_glyphs, "Rearrange glyph clusters in nominal order", nullptr},
|
||||||
|
{"verify", 0, 0, G_OPTION_ARG_NONE, &this->verify, "Perform sanity checks on shaping results", nullptr},
|
||||||
|
{"num-iterations", 'n', 0, G_OPTION_ARG_INT, &this->num_iterations, "Run shaper N times (default: 1)", "N"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries,
|
||||||
|
"shape",
|
||||||
|
"Shape options:",
|
||||||
|
"Options for the shaping process",
|
||||||
|
this);
|
||||||
|
|
||||||
|
const gchar *features_help = "Comma-separated list of font features\n"
|
||||||
|
"\n"
|
||||||
|
" Features can be enabled or disabled, either globally or limited to\n"
|
||||||
|
" specific character ranges. The format for specifying feature settings\n"
|
||||||
|
" follows. All valid CSS font-feature-settings values other than 'normal'\n"
|
||||||
|
" and the global values are also accepted, though not documented below.\n"
|
||||||
|
" CSS string escapes are not supported."
|
||||||
|
"\n"
|
||||||
|
" The range indices refer to the positions between Unicode characters,\n"
|
||||||
|
" unless the --utf8-clusters is provided, in which case range indices\n"
|
||||||
|
" refer to UTF-8 byte indices. The position before the first character\n"
|
||||||
|
" is always 0.\n"
|
||||||
|
"\n"
|
||||||
|
" The format is Python-esque. Here is how it all works:\n"
|
||||||
|
"\n"
|
||||||
|
" Syntax: Value: Start: End:\n"
|
||||||
|
"\n"
|
||||||
|
" Setting value:\n"
|
||||||
|
" \"kern\" 1 0 ∞ # Turn feature on\n"
|
||||||
|
" \"+kern\" 1 0 ∞ # Turn feature on\n"
|
||||||
|
" \"-kern\" 0 0 ∞ # Turn feature off\n"
|
||||||
|
" \"kern=0\" 0 0 ∞ # Turn feature off\n"
|
||||||
|
" \"kern=1\" 1 0 ∞ # Turn feature on\n"
|
||||||
|
" \"aalt=2\" 2 0 ∞ # Choose 2nd alternate\n"
|
||||||
|
"\n"
|
||||||
|
" Setting index:\n"
|
||||||
|
" \"kern[]\" 1 0 ∞ # Turn feature on\n"
|
||||||
|
" \"kern[:]\" 1 0 ∞ # Turn feature on\n"
|
||||||
|
" \"kern[5:]\" 1 5 ∞ # Turn feature on, partial\n"
|
||||||
|
" \"kern[:5]\" 1 0 5 # Turn feature on, partial\n"
|
||||||
|
" \"kern[3:5]\" 1 3 5 # Turn feature on, range\n"
|
||||||
|
" \"kern[3]\" 1 3 3+1 # Turn feature on, single char\n"
|
||||||
|
"\n"
|
||||||
|
" Mixing it all:\n"
|
||||||
|
"\n"
|
||||||
|
" \"aalt[3:5]=2\" 2 3 5 # Turn 2nd alternate on for range";
|
||||||
|
|
||||||
|
GOptionEntry entries2[] =
|
||||||
|
{
|
||||||
|
{"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, features_help, "list"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries2,
|
||||||
|
"features",
|
||||||
|
"Features options:",
|
||||||
|
"Options for font features used",
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -24,9 +24,51 @@
|
||||||
* Google Author(s): Garret Rieger
|
* Google Author(s): Garret Rieger
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "options.hh"
|
#ifndef SUBSET_OPTIONS_HH
|
||||||
|
#define SUBSET_OPTIONS_HH
|
||||||
|
|
||||||
|
#include "options.hh"
|
||||||
|
#include "hb-subset.h"
|
||||||
|
|
||||||
|
struct subset_options_t
|
||||||
|
{
|
||||||
|
subset_options_t ()
|
||||||
|
: input (hb_subset_input_create_or_fail ())
|
||||||
|
{}
|
||||||
|
~subset_options_t ()
|
||||||
|
{
|
||||||
|
hb_subset_input_destroy (input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_options (option_parser_t *parser);
|
||||||
|
|
||||||
|
hb_bool_t* bool_for(hb_subset_flags_t flag)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < sizeof(int) * 8; i++)
|
||||||
|
{
|
||||||
|
if (1u << i == flag)
|
||||||
|
return &flags[i];
|
||||||
|
}
|
||||||
|
return &flags[sizeof(int) * 8 - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
hb_subset_input_t * get_input ()
|
||||||
|
{
|
||||||
|
hb_subset_flags_t flags_set = HB_SUBSET_FLAGS_DEFAULT;
|
||||||
|
for (unsigned i = 0; i < sizeof(int) * 8; i++)
|
||||||
|
{
|
||||||
|
if (flags[i])
|
||||||
|
flags_set = (hb_subset_flags_t) (flags_set | (1u << i));
|
||||||
|
}
|
||||||
|
hb_subset_input_set_flags (input, flags_set);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned num_iterations = 1;
|
||||||
|
hb_subset_input_t *input = nullptr;
|
||||||
|
hb_bool_t flags[sizeof(int) * 8] = {0};
|
||||||
|
};
|
||||||
|
|
||||||
#include "hb-subset-input.hh"
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
parse_gids (const char *name G_GNUC_UNUSED,
|
parse_gids (const char *name G_GNUC_UNUSED,
|
||||||
|
@ -35,7 +77,7 @@ parse_gids (const char *name G_GNUC_UNUSED,
|
||||||
GError **error G_GNUC_UNUSED)
|
GError **error G_GNUC_UNUSED)
|
||||||
{
|
{
|
||||||
subset_options_t *subset_opts = (subset_options_t *) data;
|
subset_options_t *subset_opts = (subset_options_t *) data;
|
||||||
hb_set_t *gids = subset_opts->input->glyphs;
|
hb_set_t *gids = hb_subset_input_glyph_set (subset_opts->input);
|
||||||
|
|
||||||
char *s = (char *) arg;
|
char *s = (char *) arg;
|
||||||
char *p;
|
char *p;
|
||||||
|
@ -95,7 +137,7 @@ parse_nameids (const char *name,
|
||||||
GError **error G_GNUC_UNUSED)
|
GError **error G_GNUC_UNUSED)
|
||||||
{
|
{
|
||||||
subset_options_t *subset_opts = (subset_options_t *) data;
|
subset_options_t *subset_opts = (subset_options_t *) data;
|
||||||
hb_set_t *name_ids = subset_opts->input->name_ids;
|
hb_set_t *name_ids = hb_subset_input_nameid_set (subset_opts->input);
|
||||||
|
|
||||||
char last_name_char = name[strlen (name) - 1];
|
char last_name_char = name[strlen (name) - 1];
|
||||||
|
|
||||||
|
@ -151,7 +193,7 @@ parse_name_languages (const char *name,
|
||||||
GError **error G_GNUC_UNUSED)
|
GError **error G_GNUC_UNUSED)
|
||||||
{
|
{
|
||||||
subset_options_t *subset_opts = (subset_options_t *) data;
|
subset_options_t *subset_opts = (subset_options_t *) data;
|
||||||
hb_set_t *name_languages = subset_opts->input->name_languages;
|
hb_set_t *name_languages = hb_subset_input_namelangid_set (subset_opts->input);
|
||||||
|
|
||||||
char last_name_char = name[strlen (name) - 1];
|
char last_name_char = name[strlen (name) - 1];
|
||||||
|
|
||||||
|
@ -207,7 +249,7 @@ parse_layout_features (const char *name,
|
||||||
GError **error G_GNUC_UNUSED)
|
GError **error G_GNUC_UNUSED)
|
||||||
{
|
{
|
||||||
subset_options_t *subset_opts = (subset_options_t *) data;
|
subset_options_t *subset_opts = (subset_options_t *) data;
|
||||||
hb_set_t *layout_features = subset_opts->input->layout_features;
|
hb_set_t *layout_features = hb_subset_input_layout_features_set (subset_opts->input);
|
||||||
|
|
||||||
char last_name_char = name[strlen (name) - 1];
|
char last_name_char = name[strlen (name) - 1];
|
||||||
|
|
||||||
|
@ -256,7 +298,7 @@ parse_drop_tables (const char *name,
|
||||||
GError **error G_GNUC_UNUSED)
|
GError **error G_GNUC_UNUSED)
|
||||||
{
|
{
|
||||||
subset_options_t *subset_opts = (subset_options_t *) data;
|
subset_options_t *subset_opts = (subset_options_t *) data;
|
||||||
hb_set_t *drop_tables = subset_opts->input->drop_tables;
|
hb_set_t *drop_tables = hb_subset_input_drop_tables_set (subset_opts->input);
|
||||||
|
|
||||||
char last_name_char = name[strlen (name) - 1];
|
char last_name_char = name[strlen (name) - 1];
|
||||||
|
|
||||||
|
@ -324,3 +366,5 @@ subset_options_t::add_options (option_parser_t *parser)
|
||||||
"Options subsetting",
|
"Options subsetting",
|
||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,211 @@
|
||||||
|
|
||||||
|
struct text_options_t
|
||||||
|
{
|
||||||
|
~text_options_t ()
|
||||||
|
{
|
||||||
|
g_free (text_before);
|
||||||
|
g_free (text_after);
|
||||||
|
g_free (text);
|
||||||
|
g_free (text_file);
|
||||||
|
if (gs)
|
||||||
|
g_string_free (gs, true);
|
||||||
|
if (fp && fp != stdin)
|
||||||
|
fclose (fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_options (option_parser_t *parser);
|
||||||
|
|
||||||
|
void post_parse (GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
if (text && text_file)
|
||||||
|
g_set_error (error,
|
||||||
|
G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"Only one of text and text-file can be set");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *get_line (unsigned int *len, int eol = '\n');
|
||||||
|
|
||||||
|
char *text_before = nullptr;
|
||||||
|
char *text_after = nullptr;
|
||||||
|
|
||||||
|
int text_len = -1;
|
||||||
|
char *text = nullptr;
|
||||||
|
char *text_file = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FILE *fp = nullptr;
|
||||||
|
GString *gs = nullptr;
|
||||||
|
char *line = nullptr;
|
||||||
|
unsigned int line_len = UINT_MAX;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_text (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
text_options_t *text_opts = (text_options_t *) data;
|
||||||
|
|
||||||
|
if (text_opts->text)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"Either --text or --unicodes can be provided but not both");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
text_opts->text_len = -1;
|
||||||
|
text_opts->text = g_strdup (arg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_unicodes (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
text_options_t *text_opts = (text_options_t *) data;
|
||||||
|
|
||||||
|
if (text_opts->text)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"Either --text or --unicodes can be provided but not both");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GString *gs = g_string_new (nullptr);
|
||||||
|
if (0 == strcmp (arg, "*"))
|
||||||
|
{
|
||||||
|
g_string_append_c (gs, '*');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
char *s = (char *) arg;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
while (s && *s)
|
||||||
|
{
|
||||||
|
#define DELIMITERS "<+>{},;&#\\xXuUnNiI\n\t\v\f\r "
|
||||||
|
|
||||||
|
while (*s && strchr (DELIMITERS, *s))
|
||||||
|
s++;
|
||||||
|
if (!*s)
|
||||||
|
break;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
hb_codepoint_t u = strtoul (s, &p, 16);
|
||||||
|
if (errno || s == p)
|
||||||
|
{
|
||||||
|
g_string_free (gs, TRUE);
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"Failed parsing Unicode values at: '%s'", s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_string_append_unichar (gs, u);
|
||||||
|
|
||||||
|
s = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text_opts->text_len = gs->len;
|
||||||
|
text_opts->text = g_string_free (gs, FALSE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
text_options_t::get_line (unsigned int *len, int eol)
|
||||||
|
{
|
||||||
|
if (text)
|
||||||
|
{
|
||||||
|
if (!line)
|
||||||
|
{
|
||||||
|
line = text;
|
||||||
|
line_len = text_len;
|
||||||
|
}
|
||||||
|
if (line_len == UINT_MAX)
|
||||||
|
line_len = strlen (line);
|
||||||
|
|
||||||
|
if (!line_len) {
|
||||||
|
*len = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ret = line;
|
||||||
|
const char *p = (const char *) memchr (line, eol, line_len);
|
||||||
|
unsigned int ret_len;
|
||||||
|
if (!p)
|
||||||
|
{
|
||||||
|
ret_len = line_len;
|
||||||
|
line += ret_len;
|
||||||
|
line_len = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret_len = p - ret;
|
||||||
|
line += ret_len + 1;
|
||||||
|
line_len -= ret_len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*len = ret_len;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
if (!text_file)
|
||||||
|
fail (true, "At least one of text or text-file must be set");
|
||||||
|
|
||||||
|
if (0 != strcmp (text_file, "-"))
|
||||||
|
fp = fopen (text_file, "r");
|
||||||
|
else
|
||||||
|
fp = stdin;
|
||||||
|
|
||||||
|
if (!fp)
|
||||||
|
fail (false, "Failed opening text file `%s': %s",
|
||||||
|
text_file, strerror (errno));
|
||||||
|
|
||||||
|
gs = g_string_new (nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_string_set_size (gs, 0);
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
while (fgets (buf, sizeof (buf), fp))
|
||||||
|
{
|
||||||
|
unsigned bytes = strlen (buf);
|
||||||
|
if (bytes && (int) (unsigned char) buf[bytes - 1] == eol)
|
||||||
|
{
|
||||||
|
bytes--;
|
||||||
|
g_string_append_len (gs, buf, bytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
g_string_append_len (gs, buf, bytes);
|
||||||
|
}
|
||||||
|
if (ferror (fp))
|
||||||
|
fail (false, "Failed reading text: %s", strerror (errno));
|
||||||
|
*len = gs->len;
|
||||||
|
return !*len && feof (fp) ? nullptr : gs->str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
text_options_t::add_options (option_parser_t *parser)
|
||||||
|
{
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{"text", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Set input text", "string"},
|
||||||
|
{"text-file", 0, 0, G_OPTION_ARG_STRING, &this->text_file, "Set input text file-name\n\n If no text is provided, standard input is used for input.\n", "filename"},
|
||||||
|
{"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Set input Unicode codepoints", "list of hex numbers"},
|
||||||
|
{"text-before", 0, 0, G_OPTION_ARG_STRING, &this->text_before, "Set text context before each line", "string"},
|
||||||
|
{"text-after", 0, 0, G_OPTION_ARG_STRING, &this->text_after, "Set text context after each line", "string"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries,
|
||||||
|
"text",
|
||||||
|
"Text options:",
|
||||||
|
"Options for the input text",
|
||||||
|
this);
|
||||||
|
}
|
|
@ -1,140 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2011 Google, Inc.
|
|
||||||
*
|
|
||||||
* This is part of HarfBuzz, a text shaping library.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, without written agreement and without
|
|
||||||
* license or royalty fees, to use, copy, modify, and distribute this
|
|
||||||
* software and its documentation for any purpose, provided that the
|
|
||||||
* above copyright notice and the following two paragraphs appear in
|
|
||||||
* all copies of this software.
|
|
||||||
*
|
|
||||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
|
||||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
|
||||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
* DAMAGE.
|
|
||||||
*
|
|
||||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
|
||||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
|
||||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
|
||||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
|
||||||
*
|
|
||||||
* Google Author(s): Behdad Esfahbod
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "view-cairo.hh"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
view_cairo_t::render (const font_options_t *font_opts)
|
|
||||||
{
|
|
||||||
bool vertical = HB_DIRECTION_IS_VERTICAL (direction);
|
|
||||||
int vert = vertical ? 1 : 0;
|
|
||||||
int horiz = vertical ? 0 : 1;
|
|
||||||
|
|
||||||
int x_sign = font_opts->font_size_x < 0 ? -1 : +1;
|
|
||||||
int y_sign = font_opts->font_size_y < 0 ? -1 : +1;
|
|
||||||
|
|
||||||
hb_font_t *font = font_opts->get_font();
|
|
||||||
|
|
||||||
view_options_t::font_extents_t extents = view_options.font_extents;
|
|
||||||
if (!view_options.have_font_extents)
|
|
||||||
{
|
|
||||||
hb_font_extents_t hb_extents;
|
|
||||||
hb_font_get_extents_for_direction (font, direction, &hb_extents);
|
|
||||||
extents.ascent = scalbn ((double) hb_extents.ascender, scale_bits);
|
|
||||||
extents.descent = -scalbn ((double) hb_extents.descender, scale_bits);
|
|
||||||
extents.line_gap = scalbn ((double) hb_extents.line_gap, scale_bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
double ascent = y_sign * extents.ascent;
|
|
||||||
double descent = y_sign * extents.descent;
|
|
||||||
double line_gap = y_sign * extents.line_gap + view_options.line_space;
|
|
||||||
double leading = ascent + descent + line_gap;
|
|
||||||
|
|
||||||
/* Calculate surface size. */
|
|
||||||
double w = 0, h = 0;
|
|
||||||
(vertical ? w : h) = (int) lines->len * leading - (extents.line_gap + view_options.line_space);
|
|
||||||
(vertical ? h : w) = 0;
|
|
||||||
for (unsigned int i = 0; i < lines->len; i++) {
|
|
||||||
helper_cairo_line_t &line = g_array_index (lines, helper_cairo_line_t, i);
|
|
||||||
double x_advance, y_advance;
|
|
||||||
line.get_advance (&x_advance, &y_advance);
|
|
||||||
if (vertical)
|
|
||||||
h = MAX (h, y_sign * y_advance);
|
|
||||||
else
|
|
||||||
w = MAX (w, x_sign * x_advance);
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_scaled_font_t *scaled_font = helper_cairo_create_scaled_font (font_opts);
|
|
||||||
|
|
||||||
/* See if font needs color. */
|
|
||||||
cairo_content_t content = CAIRO_CONTENT_ALPHA;
|
|
||||||
if (helper_cairo_scaled_font_has_color (scaled_font))
|
|
||||||
content = CAIRO_CONTENT_COLOR;
|
|
||||||
|
|
||||||
/* Create surface. */
|
|
||||||
cairo_t *cr = helper_cairo_create_context (w + view_options.margin.l + view_options.margin.r,
|
|
||||||
h + view_options.margin.t + view_options.margin.b,
|
|
||||||
&view_options, &output_options, content);
|
|
||||||
cairo_set_scaled_font (cr, scaled_font);
|
|
||||||
|
|
||||||
/* Setup coordinate system. */
|
|
||||||
cairo_translate (cr, view_options.margin.l, view_options.margin.t);
|
|
||||||
if (vertical)
|
|
||||||
cairo_translate (cr,
|
|
||||||
w - ascent, /* We currently always stack lines right to left */
|
|
||||||
y_sign < 0 ? h : 0);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cairo_translate (cr,
|
|
||||||
x_sign < 0 ? w : 0,
|
|
||||||
y_sign < 0 ? descent : ascent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw. */
|
|
||||||
cairo_translate (cr, +vert * leading, -horiz * leading);
|
|
||||||
for (unsigned int i = 0; i < lines->len; i++)
|
|
||||||
{
|
|
||||||
helper_cairo_line_t &l = g_array_index (lines, helper_cairo_line_t, i);
|
|
||||||
|
|
||||||
cairo_translate (cr, -vert * leading, +horiz * leading);
|
|
||||||
|
|
||||||
if (view_options.annotate) {
|
|
||||||
cairo_save (cr);
|
|
||||||
|
|
||||||
/* Draw actual glyph origins */
|
|
||||||
cairo_set_source_rgba (cr, 1., 0., 0., .5);
|
|
||||||
cairo_set_line_width (cr, 5);
|
|
||||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
|
||||||
for (unsigned i = 0; i < l.num_glyphs; i++) {
|
|
||||||
cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y);
|
|
||||||
cairo_rel_line_to (cr, 0, 0);
|
|
||||||
}
|
|
||||||
cairo_stroke (cr);
|
|
||||||
|
|
||||||
cairo_restore (cr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 && cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) {
|
|
||||||
/* cairo_show_glyphs() doesn't support subpixel positioning */
|
|
||||||
cairo_glyph_path (cr, l.glyphs, l.num_glyphs);
|
|
||||||
cairo_fill (cr);
|
|
||||||
} else if (l.num_clusters)
|
|
||||||
cairo_show_text_glyphs (cr,
|
|
||||||
l.utf8, l.utf8_len,
|
|
||||||
l.glyphs, l.num_glyphs,
|
|
||||||
l.clusters, l.num_clusters,
|
|
||||||
l.cluster_flags);
|
|
||||||
else
|
|
||||||
cairo_show_glyphs (cr, l.glyphs, l.num_glyphs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clean up. */
|
|
||||||
helper_cairo_destroy_context (cr);
|
|
||||||
cairo_scaled_font_destroy (scaled_font);
|
|
||||||
}
|
|
|
@ -27,22 +27,23 @@
|
||||||
#ifndef VIEW_CAIRO_HH
|
#ifndef VIEW_CAIRO_HH
|
||||||
#define VIEW_CAIRO_HH
|
#define VIEW_CAIRO_HH
|
||||||
|
|
||||||
#include "hb.hh"
|
#include "view-options.hh"
|
||||||
#include "options.hh"
|
#include "output-options.hh"
|
||||||
#include "helper-cairo.hh"
|
#include "helper-cairo.hh"
|
||||||
|
|
||||||
|
struct view_cairo_t : view_options_t, output_options_t
|
||||||
struct view_cairo_t
|
|
||||||
{
|
{
|
||||||
view_cairo_t (option_parser_t *parser)
|
~view_cairo_t ()
|
||||||
: output_options (parser, helper_cairo_supported_formats),
|
{
|
||||||
view_options (parser),
|
|
||||||
direction (HB_DIRECTION_INVALID),
|
|
||||||
lines (0), scale_bits (0) {}
|
|
||||||
~view_cairo_t () {
|
|
||||||
cairo_debug_reset_static_data ();
|
cairo_debug_reset_static_data ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_options (option_parser_t *parser)
|
||||||
|
{
|
||||||
|
view_options_t::add_options (parser);
|
||||||
|
output_options_t::add_options (parser, helper_cairo_supported_formats);
|
||||||
|
}
|
||||||
|
|
||||||
void init (hb_buffer_t *buffer, const font_options_t *font_opts)
|
void init (hb_buffer_t *buffer, const font_options_t *font_opts)
|
||||||
{
|
{
|
||||||
lines = g_array_new (false, false, sizeof (helper_cairo_line_t));
|
lines = g_array_new (false, false, sizeof (helper_cairo_line_t));
|
||||||
|
@ -82,14 +83,123 @@ struct view_cairo_t
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
output_options_t output_options;
|
|
||||||
view_options_t view_options;
|
|
||||||
|
|
||||||
void render (const font_options_t *font_opts);
|
void render (const font_options_t *font_opts);
|
||||||
|
|
||||||
hb_direction_t direction; // Remove this, make segment_properties accessible
|
hb_direction_t direction = HB_DIRECTION_INVALID; // Remove this, make segment_properties accessible
|
||||||
GArray *lines;
|
GArray *lines = nullptr;
|
||||||
int scale_bits;
|
int scale_bits = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline void
|
||||||
|
view_cairo_t::render (const font_options_t *font_opts)
|
||||||
|
{
|
||||||
|
bool vertical = HB_DIRECTION_IS_VERTICAL (direction);
|
||||||
|
int vert = vertical ? 1 : 0;
|
||||||
|
int horiz = vertical ? 0 : 1;
|
||||||
|
|
||||||
|
int x_sign = font_opts->font_size_x < 0 ? -1 : +1;
|
||||||
|
int y_sign = font_opts->font_size_y < 0 ? -1 : +1;
|
||||||
|
|
||||||
|
hb_font_t *font = font_opts->get_font();
|
||||||
|
|
||||||
|
if (!have_font_extents)
|
||||||
|
{
|
||||||
|
hb_font_extents_t hb_extents;
|
||||||
|
hb_font_get_extents_for_direction (font, direction, &hb_extents);
|
||||||
|
font_extents.ascent = scalbn ((double) hb_extents.ascender, scale_bits);
|
||||||
|
font_extents.descent = -scalbn ((double) hb_extents.descender, scale_bits);
|
||||||
|
font_extents.line_gap = scalbn ((double) hb_extents.line_gap, scale_bits);
|
||||||
|
have_font_extents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ascent = y_sign * font_extents.ascent;
|
||||||
|
double descent = y_sign * font_extents.descent;
|
||||||
|
double line_gap = y_sign * font_extents.line_gap + line_space;
|
||||||
|
double leading = ascent + descent + line_gap;
|
||||||
|
|
||||||
|
/* Calculate surface size. */
|
||||||
|
double w = 0, h = 0;
|
||||||
|
(vertical ? w : h) = (int) lines->len * leading - (font_extents.line_gap + line_space);
|
||||||
|
(vertical ? h : w) = 0;
|
||||||
|
for (unsigned int i = 0; i < lines->len; i++) {
|
||||||
|
helper_cairo_line_t &line = g_array_index (lines, helper_cairo_line_t, i);
|
||||||
|
double x_advance, y_advance;
|
||||||
|
line.get_advance (&x_advance, &y_advance);
|
||||||
|
if (vertical)
|
||||||
|
h = MAX (h, y_sign * y_advance);
|
||||||
|
else
|
||||||
|
w = MAX (w, x_sign * x_advance);
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_scaled_font_t *scaled_font = helper_cairo_create_scaled_font (font_opts);
|
||||||
|
|
||||||
|
/* See if font needs color. */
|
||||||
|
cairo_content_t content = CAIRO_CONTENT_ALPHA;
|
||||||
|
if (helper_cairo_scaled_font_has_color (scaled_font))
|
||||||
|
content = CAIRO_CONTENT_COLOR;
|
||||||
|
|
||||||
|
/* Create surface. */
|
||||||
|
cairo_t *cr = helper_cairo_create_context (w + margin.l + margin.r,
|
||||||
|
h + margin.t + margin.b,
|
||||||
|
static_cast<view_options_t *> (this),
|
||||||
|
static_cast<output_options_t *> (this),
|
||||||
|
content);
|
||||||
|
cairo_set_scaled_font (cr, scaled_font);
|
||||||
|
|
||||||
|
/* Setup coordinate system. */
|
||||||
|
cairo_translate (cr, margin.l, margin.t);
|
||||||
|
if (vertical)
|
||||||
|
cairo_translate (cr,
|
||||||
|
w - ascent, /* We currently always stack lines right to left */
|
||||||
|
y_sign < 0 ? h : 0);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cairo_translate (cr,
|
||||||
|
x_sign < 0 ? w : 0,
|
||||||
|
y_sign < 0 ? descent : ascent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw. */
|
||||||
|
cairo_translate (cr, +vert * leading, -horiz * leading);
|
||||||
|
for (unsigned int i = 0; i < lines->len; i++)
|
||||||
|
{
|
||||||
|
helper_cairo_line_t &l = g_array_index (lines, helper_cairo_line_t, i);
|
||||||
|
|
||||||
|
cairo_translate (cr, -vert * leading, +horiz * leading);
|
||||||
|
|
||||||
|
if (annotate) {
|
||||||
|
cairo_save (cr);
|
||||||
|
|
||||||
|
/* Draw actual glyph origins */
|
||||||
|
cairo_set_source_rgba (cr, 1., 0., 0., .5);
|
||||||
|
cairo_set_line_width (cr, 5);
|
||||||
|
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
||||||
|
for (unsigned i = 0; i < l.num_glyphs; i++) {
|
||||||
|
cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y);
|
||||||
|
cairo_rel_line_to (cr, 0, 0);
|
||||||
|
}
|
||||||
|
cairo_stroke (cr);
|
||||||
|
|
||||||
|
cairo_restore (cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 && cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) {
|
||||||
|
/* cairo_show_glyphs() doesn't support subpixel positioning */
|
||||||
|
cairo_glyph_path (cr, l.glyphs, l.num_glyphs);
|
||||||
|
cairo_fill (cr);
|
||||||
|
} else if (l.num_clusters)
|
||||||
|
cairo_show_text_glyphs (cr,
|
||||||
|
l.utf8, l.utf8_len,
|
||||||
|
l.glyphs, l.num_glyphs,
|
||||||
|
l.clusters, l.num_clusters,
|
||||||
|
l.cluster_flags);
|
||||||
|
else
|
||||||
|
cairo_show_glyphs (cr, l.glyphs, l.num_glyphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clean up. */
|
||||||
|
helper_cairo_destroy_context (cr);
|
||||||
|
cairo_scaled_font_destroy (scaled_font);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Google, Inc.
|
||||||
|
*
|
||||||
|
* This is part of HarfBuzz, a text shaping library.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, without written agreement and without
|
||||||
|
* license or royalty fees, to use, copy, modify, and distribute this
|
||||||
|
* software and its documentation for any purpose, provided that the
|
||||||
|
* above copyright notice and the following two paragraphs appear in
|
||||||
|
* all copies of this software.
|
||||||
|
*
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||||
|
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||||
|
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||||
|
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||||
|
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||||
|
*
|
||||||
|
* Google Author(s): Behdad Esfahbod
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VIEW_OPTIONS_HH
|
||||||
|
#define VIEW_OPTIONS_HH
|
||||||
|
|
||||||
|
#include "options.hh"
|
||||||
|
|
||||||
|
#define DEFAULT_MARGIN 16
|
||||||
|
#define DEFAULT_FORE "#000000"
|
||||||
|
#define DEFAULT_BACK "#FFFFFF"
|
||||||
|
|
||||||
|
struct view_options_t
|
||||||
|
{
|
||||||
|
~view_options_t ()
|
||||||
|
{
|
||||||
|
g_free (fore);
|
||||||
|
g_free (back);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_options (option_parser_t *parser);
|
||||||
|
|
||||||
|
hb_bool_t annotate = false;
|
||||||
|
char *fore = nullptr;
|
||||||
|
char *back = nullptr;
|
||||||
|
double line_space = 0;
|
||||||
|
bool have_font_extents = false;
|
||||||
|
struct font_extents_t {
|
||||||
|
double ascent, descent, line_gap;
|
||||||
|
} font_extents = {0., 0., 0.};
|
||||||
|
struct margin_t {
|
||||||
|
double t, r, b, l;
|
||||||
|
} margin = {DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_font_extents (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
view_options_t *view_opts = (view_options_t *) data;
|
||||||
|
view_options_t::font_extents_t &e = view_opts->font_extents;
|
||||||
|
switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf", &e.ascent, &e.descent, &e.line_gap)) {
|
||||||
|
case 1: HB_FALLTHROUGH;
|
||||||
|
case 2: HB_FALLTHROUGH;
|
||||||
|
case 3:
|
||||||
|
view_opts->have_font_extents = true;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"%s argument should be one to three space-separated numbers",
|
||||||
|
name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_margin (const char *name G_GNUC_UNUSED,
|
||||||
|
const char *arg,
|
||||||
|
gpointer data,
|
||||||
|
GError **error G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
view_options_t *view_opts = (view_options_t *) data;
|
||||||
|
view_options_t::margin_t &m = view_opts->margin;
|
||||||
|
switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", &m.t, &m.r, &m.b, &m.l)) {
|
||||||
|
case 1: m.r = m.t; HB_FALLTHROUGH;
|
||||||
|
case 2: m.b = m.t; HB_FALLTHROUGH;
|
||||||
|
case 3: m.l = m.r; HB_FALLTHROUGH;
|
||||||
|
case 4: return true;
|
||||||
|
default:
|
||||||
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
||||||
|
"%s argument should be one to four space-separated numbers",
|
||||||
|
name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
view_options_t::add_options (option_parser_t *parser)
|
||||||
|
{
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{"annotate", 0, 0, G_OPTION_ARG_NONE, &this->annotate, "Annotate output rendering", nullptr},
|
||||||
|
{"background", 0, 0, G_OPTION_ARG_STRING, &this->back, "Set background color (default: " DEFAULT_BACK ")", "rrggbb/rrggbbaa"},
|
||||||
|
{"foreground", 0, 0, G_OPTION_ARG_STRING, &this->fore, "Set foreground color (default: " DEFAULT_FORE ")", "rrggbb/rrggbbaa"},
|
||||||
|
{"line-space", 0, 0, G_OPTION_ARG_DOUBLE, &this->line_space, "Set space between lines (default: 0)", "units"},
|
||||||
|
{"font-extents", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_extents, "Set font ascent/descent/line-gap (default: auto)","one to three numbers"},
|
||||||
|
{"margin", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_margin, "Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"},
|
||||||
|
{nullptr}
|
||||||
|
};
|
||||||
|
parser->add_group (entries,
|
||||||
|
"view",
|
||||||
|
"View options:",
|
||||||
|
"Options for output rendering",
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue