Skip to content

Commit 86fb4ca

Browse files
committed
text: add extended language property support
1 parent d1542ce commit 86fb4ca

File tree

8 files changed

+239
-4
lines changed

8 files changed

+239
-4
lines changed

examples/heif_info.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@
3838
#endif
3939

4040
#include <libheif/heif.h>
41+
#include <libheif/heif_library.h>
4142
#include <libheif/heif_regions.h>
4243
#include <libheif/heif_properties.h>
4344
#include <libheif/heif_experimental.h>
4445
#include "libheif/heif_sequences.h"
46+
#include <libheif/heif_text.h>
4547

4648
#include <fstream>
4749
#include <iostream>
@@ -639,6 +641,44 @@ int main(int argc, char** argv)
639641
printf(" none\n");
640642
}
641643

644+
// --- text items
645+
int numTextItems = heif_image_handle_get_number_of_text_items(handle);
646+
printf("text items:\n");
647+
648+
if (numTextItems > 0) {
649+
std::vector<heif_item_id> text_items(numTextItems);
650+
heif_image_handle_get_list_of_text_item_ids(handle, text_items.data(), numTextItems);
651+
for (heif_item_id text_item_id : text_items) {
652+
struct heif_text_item* text_item;
653+
err = heif_context_get_text_item(ctx.get(), text_item_id, &text_item);
654+
const char* text_content = heif_text_item_get_content(text_item);
655+
printf(" text item: %s\n", text_content);
656+
heif_string_release(text_content);
657+
658+
heif_property_id properties[MAX_PROPERTIES];
659+
int nDescr = heif_item_get_properties_of_type(ctx.get(),
660+
text_item_id,
661+
heif_item_property_type_extended_language,
662+
properties,
663+
MAX_PROPERTIES);
664+
heif_text_item_release(text_item);
665+
for (int k = 0; k < nDescr; k++) {
666+
char* elng;
667+
err = heif_item_get_property_extended_language(ctx.get(),
668+
text_item_id,
669+
properties[k],
670+
&elng);
671+
if (err.code == 0) {
672+
printf(" extended language: %s\n", elng);
673+
}
674+
heif_string_release(elng);
675+
}
676+
}
677+
}
678+
else {
679+
printf(" none\n");
680+
}
681+
642682
// --- properties
643683

644684
printf("properties:\n");

libheif/Doxyfile.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,8 @@ WARN_LOGFILE =
858858

859859
INPUT = @CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif.h \
860860
@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_items.h \
861-
@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_regions.h
861+
@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_regions.h \
862+
@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_text.h
862863

863864
# This tag can be used to specify the character encoding of the source files
864865
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

libheif/api/libheif/heif_properties.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ enum heif_item_property_type
4040
heif_item_property_type_image_size = heif_fourcc('i', 's', 'p', 'e'),
4141
heif_item_property_type_uuid = heif_fourcc('u', 'u', 'i', 'd'),
4242
heif_item_property_type_tai_clock_info = heif_fourcc('t', 'a', 'i', 'c'),
43-
heif_item_property_type_tai_timestamp = heif_fourcc('i', 't', 'a', 'i')
43+
heif_item_property_type_tai_timestamp = heif_fourcc('i', 't', 'a', 'i'),
44+
heif_item_property_type_extended_language = heif_fourcc('e', 'l', 'n', 'g')
4445
};
4546

4647
// Get the heif_property_id for a heif_item_id.

libheif/api/libheif/heif_text.cc

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "heif_text.h"
2323
#include "api_structs.h"
24+
#include "file.h"
2425
#include "text.h"
2526
#include <algorithm>
2627
#include <cstring>
@@ -114,3 +115,61 @@ const char* heif_text_item_get_content(struct heif_text_item* text_item)
114115

115116
return text_c;
116117
}
118+
119+
struct heif_error heif_item_get_property_extended_language(const heif_context* context,
120+
heif_item_id itemId,
121+
heif_property_id propertyId,
122+
char** out_language)
123+
{
124+
if (!out_language || !context) {
125+
return {heif_error_Usage_error, heif_suberror_Invalid_parameter_value, "NULL passed"};
126+
}
127+
128+
auto file = context->context->get_heif_file();
129+
130+
std::vector<std::shared_ptr<Box>> properties;
131+
Error err = file->get_properties(itemId, properties);
132+
if (err) {
133+
return err.error_struct(context->context.get());
134+
}
135+
136+
if (propertyId < 1 || propertyId - 1 >= properties.size()) {
137+
return {heif_error_Usage_error, heif_suberror_Invalid_property, "property index out of range"};
138+
}
139+
140+
auto elng = std::dynamic_pointer_cast<Box_elng>(properties[propertyId - 1]);
141+
if (!elng) {
142+
return {heif_error_Usage_error, heif_suberror_Invalid_property, "wrong property type"};
143+
}
144+
145+
std::string lang = elng->get_extended_language();
146+
*out_language = new char[lang.length() + 1];
147+
strcpy(*out_language, lang.c_str());
148+
149+
return heif_error_success;
150+
}
151+
152+
struct heif_error heif_text_item_add_extended_language(heif_text_item* text_item, const char *language, heif_property_id* out_optional_propertyId)
153+
{
154+
if (!text_item || !language) {
155+
return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, "NULL passed"};
156+
}
157+
158+
if (auto img = text_item->context->get_image(text_item->text_item->get_item_id(), false)) {
159+
auto existing_elng = img->get_property<Box_elng>();
160+
if (existing_elng) {
161+
return {heif_error_Usage_error, heif_suberror_Invalid_parameter_value, "item already has an elng property"};
162+
}
163+
}
164+
165+
auto elng = std::make_shared<Box_elng>();
166+
elng->set_lang(std::string(language));
167+
168+
heif_property_id id = text_item->context->add_property(text_item->text_item->get_item_id(), elng, false);
169+
170+
if (out_optional_propertyId) {
171+
*out_optional_propertyId = id;
172+
}
173+
174+
return heif_error_success;
175+
}

libheif/api/libheif/heif_text.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ heif_error heif_context_get_text_item(const heif_context* context,
8585
/**
8686
* Get the item identifier for a text item.
8787
*
88-
* @param region_item the text item to query
88+
* @param text_item the text item to query
8989
* @return the text item identifier (or 0 if the text_item is null)
9090
*/
9191
LIBHEIF_API
@@ -103,6 +103,21 @@ heif_item_id heif_text_item_get_id(heif_text_item* text_item);
103103
LIBHEIF_API
104104
const char* heif_text_item_get_content(heif_text_item* text_item);
105105

106+
/**
107+
* Get the extended language associated with the text item.
108+
*
109+
* @param context the context to get the text item from, usually from a file operation
110+
* @param itemId the identifier for the text item
111+
* @param propertyId the property identifier
112+
* @param out_language pointer to pointer to the resulting language
113+
* @return heif_error_ok on success, or an error value indicating the problem
114+
*/
115+
LIBHEIF_API
116+
heif_error heif_item_get_property_extended_language(const heif_context* context,
117+
heif_item_id itemId,
118+
heif_property_id propertyId,
119+
char** out_language);
120+
106121
// --- adding text items
107122

108123
/**
@@ -124,6 +139,22 @@ heif_error heif_image_handle_add_text_item(heif_image_handle *image_handle,
124139
LIBHEIF_API
125140
void heif_text_item_release(heif_text_item* text_item);
126141

142+
/**
143+
* Add an extended language property to the text item.
144+
*
145+
* This adds an RFC 5346 (IETF BCP 47) extended language tag, such as "en-AU".
146+
*
147+
* @param text_item the text item to query
148+
* @param language the language to add
149+
* @param out_optional_propertyId Output parameter for the property ID of the language property.
150+
* This parameter may be NULL if the info is not required.
151+
* @return heif_error_ok on success, or an error value indicating the problem
152+
*/
153+
LIBHEIF_API
154+
heif_error heif_text_item_add_extended_language(heif_text_item* text_item,
155+
const char *language,
156+
heif_property_id* out_optional_propertyId);
157+
127158
#ifdef __cplusplus
128159
}
129160
#endif

libheif/box.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,11 @@ Error Box::read(BitstreamRange& range, std::shared_ptr<Box>* result, const heif_
646646
box = std::make_shared<Box_mjpg>();
647647
break;
648648

649+
case fourcc("elng"):
650+
box = std::make_shared<Box_elng>();
651+
break;
652+
653+
649654
#if WITH_UNCOMPRESSED_CODEC
650655
case fourcc("cmpd"):
651656
box = std::make_shared<Box_cmpd>();
@@ -4990,3 +4995,30 @@ Error Box_itai::parse(BitstreamRange& range, const heif_security_limits*) {
49904995
return range.get_error();
49914996
}
49924997

4998+
Error Box_elng::parse(BitstreamRange& range, const heif_security_limits* limits)
4999+
{
5000+
parse_full_box_header(range);
5001+
5002+
if (get_version() > 0) {
5003+
return unsupported_version_error("elng");
5004+
}
5005+
5006+
m_lang = range.read_string();
5007+
return range.get_error();
5008+
}
5009+
5010+
std::string Box_elng::dump(Indent& indent) const
5011+
{
5012+
std::ostringstream sstr;
5013+
sstr << Box::dump(indent);
5014+
sstr << indent << "extended_language: " << m_lang << "\n";
5015+
return sstr.str();
5016+
}
5017+
5018+
Error Box_elng::write(StreamWriter& writer) const
5019+
{
5020+
size_t box_start = reserve_box_header_space(writer);
5021+
writer.write(m_lang);
5022+
prepend_header(writer, box_start);
5023+
return Error::Ok;
5024+
}

libheif/box.h

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1818,4 +1818,51 @@ class Box_itai : public FullBox
18181818
bool operator==(const heif_tai_timestamp_packet& a,
18191819
const heif_tai_timestamp_packet& b);
18201820

1821-
#endif
1821+
1822+
/**
1823+
* Extended language property.
1824+
*
1825+
* Permits the association of language information with an item.
1826+
*
1827+
* See ISO/IEC 23008-12:2025(E) Section 6.10.2.2 and ISO/IEC 14496-12:2022(E) Section 8.4.6.
1828+
*/
1829+
class Box_elng : public FullBox
1830+
{
1831+
public:
1832+
Box_elng()
1833+
{
1834+
set_short_type(fourcc("elng"));
1835+
}
1836+
1837+
std::string dump(Indent&) const override;
1838+
1839+
const char* debug_box_name() const override { return "Extended language"; }
1840+
1841+
Error write(StreamWriter& writer) const override;
1842+
1843+
/**
1844+
* Language.
1845+
*
1846+
* An RFC 5646 (IETF BCP 47) compliant language identifier for the language of the text.
1847+
* Examples: "en-AU", "de-DE", or "zh-CN“.
1848+
*/
1849+
std::string get_extended_language() const { return m_lang; }
1850+
1851+
/**
1852+
* Set the language.
1853+
*
1854+
* An RFC 5646 (IETF BCP 47) compliant language identifier for the language of the text.
1855+
* Examples: "en-AU", "de-DE", or "zh-CN“.
1856+
*/
1857+
void set_lang(const std::string lang) { m_lang = lang; }
1858+
1859+
[[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; }
1860+
1861+
protected:
1862+
Error parse(BitstreamRange& range, const heif_security_limits*) override;
1863+
1864+
private:
1865+
std::string m_lang;
1866+
};
1867+
1868+
#endif

tests/text.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <cstdint>
3434
#include <cstring>
3535
#include <libheif/heif_items.h>
36+
#include <libheif/heif_library.h>
3637

3738

3839
TEST_CASE("no text") {
@@ -83,11 +84,17 @@ TEST_CASE("create text item") {
8384
std::string text_body1("first string");
8485
err = heif_image_handle_add_text_item(handle, "text/plain", text_body1.c_str(), &text_item1);
8586
REQUIRE(err.code == heif_error_Ok);
87+
err = heif_text_item_add_extended_language(text_item1, "en-AU", NULL);
88+
REQUIRE(err.code == heif_error_Ok);
8689

8790
struct heif_text_item* text_item2;
8891
std::string text_body2("a second string");
8992
err = heif_image_handle_add_text_item(handle, "text/plain", text_body2.c_str(), &text_item2);
9093
REQUIRE(err.code == heif_error_Ok);
94+
heif_property_id elng_prop_id;
95+
err = heif_text_item_add_extended_language(text_item2, "en-UK", &elng_prop_id);
96+
REQUIRE(err.code == heif_error_Ok);
97+
REQUIRE(elng_prop_id != 0);
9198

9299
err = heif_context_write_to_file(ctx, "text.heif");
93100
REQUIRE(err.code == heif_error_Ok);
@@ -128,6 +135,15 @@ TEST_CASE("create text item") {
128135
REQUIRE(std::string(content_type0) == "text/plain");
129136
const char* body0 = heif_text_item_get_content(text0);
130137
REQUIRE(std::string(body0) == text_body1);
138+
heif_string_release(body0);
139+
char * elng0;
140+
heif_property_id properties[3];
141+
int num_props = heif_item_get_properties_of_type(readbackCtx, id0, heif_item_property_type_extended_language, properties, 3);
142+
REQUIRE(num_props == 1);
143+
err = heif_item_get_property_extended_language(readbackCtx, id0, properties[0], &elng0);
144+
REQUIRE(err.code == heif_error_Ok);
145+
REQUIRE(strcmp(elng0, "en-AU") == 0);
146+
heif_string_release(elng0);
131147

132148
heif_text_item* text1;
133149
err = heif_context_get_text_item(readbackCtx, text_item_ids[1], &text1);
@@ -139,6 +155,14 @@ TEST_CASE("create text item") {
139155
REQUIRE(std::string(content_type1) == "text/plain");
140156
const char* body1 = heif_text_item_get_content(text1);
141157
REQUIRE(std::string(body1) == text_body2);
158+
heif_string_release(body1);
159+
char * elng1;
160+
num_props = heif_item_get_properties_of_type(readbackCtx, id1, heif_item_property_type_extended_language, properties, 3);
161+
REQUIRE(num_props == 1);
162+
err = heif_item_get_property_extended_language(readbackCtx, id1, properties[0], &elng1);
163+
REQUIRE(err.code == heif_error_Ok);
164+
REQUIRE(strcmp(elng1, "en-UK") == 0);
165+
heif_string_release(elng1);
142166

143167
heif_text_item_release(text0);
144168
heif_text_item_release(text1);

0 commit comments

Comments
 (0)