blob: 1330988ccf063cfd530d26df19e30eafeb085039 [file] [log] [blame] [edit]
//========================================================================
//
// This file is under the GPLv2 or later license
//
// Copyright (C) 2005-2006 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2005, 2009 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2011 Simon Kellner <kellner@kit.edu>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include <config.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "PageLabelInfo.h"
/* http://mathworld.wolfram.com/RomanNumerals.html */
static int fromRoman(const char *buffer) {
int digit_value, prev_digit_value, value;
int i;
prev_digit_value = INT_MAX;
value = 0;
for (i = 0; buffer[i] != '\0'; i++) {
switch (buffer[i]) {
case 'm':
case 'M':
digit_value = 1000;
break;
case 'd':
case 'D':
digit_value = 500;
break;
case 'c':
case 'C':
digit_value = 100;
break;
case 'l':
case 'L':
digit_value = 50;
break;
case 'x':
case 'X':
digit_value = 10;
break;
case 'v':
case 'V':
digit_value = 5;
break;
case 'i':
case 'I':
digit_value = 1;
break;
default:
return -1;
}
if (digit_value <= prev_digit_value)
value += digit_value;
else
value += digit_value - prev_digit_value * 2;
prev_digit_value = digit_value;
}
return value;
}
static void toRoman(int number, GooString *str, GBool uppercase) {
static const char uppercaseNumerals[] = "IVXLCDM";
static const char lowercaseNumerals[] = "ivxlcdm";
int divisor;
int i, j, k;
const char *wh;
if (uppercase)
wh = uppercaseNumerals;
else
wh = lowercaseNumerals;
divisor = 1000;
for (k = 3; k >= 0; k--) {
i = number / divisor;
number = number % divisor;
switch (i) {
case 0:
break;
case 5:
str->append(wh[2 * k + 1]);
break;
case 9:
str->append(wh[2 * k + 0]);
str->append(wh[ 2 * k + 2]);
break;
case 4:
str->append(wh[2 * k + 0]);
str->append(wh[2 * k + 1]);
break;
default:
if (i > 5) {
str->append(wh[2 * k + 1]);
i -= 5;
}
for (j = 0; j < i; j++) {
str->append(wh[2 * k + 0]);
}
}
divisor = divisor / 10;
}
}
static int fromLatin(const char *buffer)
{
int count;
const char *p;
for (p = buffer; *p; p++) {
if (*p != buffer[0])
return -1;
}
count = p - buffer;
if (buffer[0] >= 'a' && buffer[0] <= 'z')
return 26 * (count - 1) + buffer[0] - 'a' + 1;
if (buffer[0] >= 'A' && buffer[0] <= 'Z')
return 26 * (count - 1) + buffer[0] - 'A' + 1;
return -1;
}
#ifdef TEST
static void toLatin(int number, GooString *str, GBool uppercase) {
char base, letter;
int i, count;
if (uppercase)
base = 'A';
else
base = 'a';
count = (number - 1) / 26 + 1;
letter = base + (number - 1) % 26;
for (i = 0; i < count; i++)
str->append(letter);
}
#endif
PageLabelInfo::Interval::Interval(Object *dict, int baseA) {
Object obj;
style = None;
if (dict->dictLookup("S", &obj)->isName()) {
if (obj.isName("D")) {
style = Arabic;
} else if (obj.isName("R")) {
style = UppercaseRoman;
} else if (obj.isName("r")) {
style = LowercaseRoman;
} else if (obj.isName("A")) {
style = UppercaseLatin;
} else if (obj.isName("a")) {
style = LowercaseLatin;
}
}
obj.free();
if (dict->dictLookup("P", &obj)->isString())
prefix = obj.getString()->copy();
else
prefix = new GooString("");
obj.free();
if (dict->dictLookup("St", &obj)->isInt())
first = obj.getInt();
else
first = 1;
obj.free();
base = baseA;
}
PageLabelInfo::Interval::~Interval() {
delete prefix;
}
PageLabelInfo::PageLabelInfo(Object *tree, int numPages) {
int i;
Interval *interval, *next;
parse(tree);
for (i = 0; i < intervals.getLength(); i++) {
interval = (Interval *) intervals.get(i);
if (i + 1 < intervals.getLength()) {
next = (Interval *) intervals.get(i + 1);
interval->length = next->base - interval->base;
} else {
interval->length = numPages - interval->base;
}
if (interval->length < 0) interval->length = 0;
}
}
PageLabelInfo::~PageLabelInfo() {
int i;
for (i = 0; i < intervals.getLength(); ++i) {
delete (Interval*)intervals.get(i);
}
}
void PageLabelInfo::parse(Object *tree) {
Object nums, obj;
Object kids, kid, limits, low, high;
int i, base;
Interval *interval;
// leaf node
if (tree->dictLookup("Nums", &nums)->isArray()) {
for (i = 0; i < nums.arrayGetLength(); i += 2) {
if (!nums.arrayGet(i, &obj)->isInt()) {
obj.free();
continue;
}
base = obj.getInt();
obj.free();
if (!nums.arrayGet(i + 1, &obj)->isDict()) {
obj.free();
continue;
}
interval = new Interval(&obj, base);
obj.free();
intervals.append(interval);
}
}
nums.free();
if (tree->dictLookup("Kids", &kids)->isArray()) {
for (i = 0; i < kids.arrayGetLength(); ++i) {
if (kids.arrayGet(i, &kid)->isDict())
parse(&kid);
kid.free();
}
}
kids.free();
}
GBool PageLabelInfo::labelToIndex(GooString *label, int *index)
{
Interval *interval;
char *str = label->getCString(), *end;
int prefixLength;
int i, number;
for (i = 0; i < intervals.getLength(); i++) {
interval = (Interval *) intervals.get(i);
const int base = interval->base;
prefixLength = interval->prefix->getLength();
if (label->cmpN(interval->prefix, prefixLength) != 0)
continue;
switch (interval->style) {
case Interval::Arabic:
number = strtol(str + prefixLength, &end, 10);
if (*end == '\0' && number - interval->first < interval->length) {
*index = base + number - interval->first;
return gTrue;
}
break;
case Interval::LowercaseRoman:
case Interval::UppercaseRoman:
number = fromRoman(str + prefixLength);
if (number >= 0 && number - interval->first < interval->length) {
*index = base + number - interval->first;
return gTrue;
}
break;
case Interval::UppercaseLatin:
case Interval::LowercaseLatin:
number = fromLatin(str + prefixLength);
if (number >= 0 && number - interval->first < interval->length) {
*index = base + number - interval->first;
return gTrue;
}
break;
case Interval::None:
break;
}
}
return gFalse;
}
GBool PageLabelInfo::indexToLabel(int index, GooString *label)
{
char buffer[32];
int i, base, number;
Interval *interval;
GooString number_string;
base = 0;
interval = NULL;
for (i = 0; i < intervals.getLength(); i++) {
interval = (Interval *) intervals.get(i);
if (base <= index && index < base + interval->length)
break;
base += interval->length;
}
if (i == intervals.getLength())
return gFalse;
number = index - base + interval->first;
switch (interval->style) {
case Interval::Arabic:
snprintf (buffer, sizeof(buffer), "%d", number);
number_string.append(buffer);
break;
case Interval::LowercaseRoman:
toRoman(number, &number_string, gFalse);
break;
case Interval::UppercaseRoman:
toRoman(number, &number_string, gTrue);
break;
case Interval::UppercaseLatin:
case Interval::LowercaseLatin:
number = 0;
break;
case Interval::None:
break;
}
label->clear();
label->append(interval->prefix);
if (label->hasUnicodeMarker()) {
int i, len;
char ucs2_char[2];
/* Convert the ascii number string to ucs2 and append. */
len = number_string.getLength ();
ucs2_char[0] = 0;
for (i = 0; i < len; ++i) {
ucs2_char[1] = number_string.getChar(i);
label->append(ucs2_char, 2);
}
ucs2_char[1] = 0;
label->append(ucs2_char, 2);
} else {
label->append(&number_string);
}
return gTrue;
}
#ifdef TEST
int main(int argc, char *argv[])
{
{
GooString str;
toRoman(177, &str, gFalse);
assert (str.cmp("clxxvii") == 0);
}
{
GooString roman("clxxvii");
assert (fromRoman(roman.getCString()) == 177);
}
{
GooString str;
toLatin(54, &str, gFalse);
assert (str.cmp("bbb") == 0);
}
{
GooString latin("ddd");
assert (fromLatin(latin.getCString()) == 56);
}
}
#endif